summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Härdeman <david@hardeman.nu>2020-06-05 14:09:18 +0200
committerDavid Härdeman <david@hardeman.nu>2020-06-05 14:09:18 +0200
commitdb66484c4300f5f0e857eff01d15fd3593002a79 (patch)
treea787b9f0da1243ae0391d5931ecb9cb0f29d3ee4
Initial commit
-rw-r--r--config.c730
-rw-r--r--config.h12
-rw-r--r--ctest.c74
-rw-r--r--inotify.c730
-rw-r--r--inotify.h12
-rw-r--r--main.c284
-rw-r--r--main.h55
-rw-r--r--meson.build9
-rw-r--r--server.c423
-rw-r--r--server.h51
-rw-r--r--stest.c102
-rw-r--r--uring.c294
-rw-r--r--uring.h41
-rw-r--r--utils.c37
-rw-r--r--utils.h138
15 files changed, 2992 insertions, 0 deletions
diff --git a/config.c b/config.c
new file mode 100644
index 0000000..f362920
--- /dev/null
+++ b/config.c
@@ -0,0 +1,730 @@
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdbool.h>
+#include <sys/inotify.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <inttypes.h>
+
+#include "main.h"
+#include "uring.h"
+#include "config.h"
+#include "server.h"
+
+static void
+eat_whitespace_and_comments(char **pos)
+{
+ while (true) {
+ while (isspace(**pos))
+ (*pos)++;
+
+ if (**pos == '#') {
+ while (**pos != '\r' && **pos != '\n' && **pos != '\0')
+ (*pos)++;
+ continue;
+ }
+
+ return;
+ }
+}
+
+static char *
+get_line(char **pos)
+{
+ char *begin = *pos;
+ char *end;
+
+ while (isspace(*begin))
+ begin++;
+
+ if (*begin == '\0')
+ return NULL;
+
+ end = begin;
+ while (*end != '\n' && *end != '\0')
+ end++;
+
+ if (*end == '\0')
+ *pos = end;
+ else
+ *pos = end + 1;
+
+ while (isspace(*end)) {
+ *end = '\0';
+ end--;
+ }
+
+ return begin;
+}
+
+static bool
+strtosockaddrs(const char *str, struct list_head *list)
+{
+ char *tmp;
+ uint16_t port;
+ int r;
+ struct sockaddr_in46 *addr;
+
+ if (!str || *str == '\0' || !list)
+ return false;
+ list_init(list);
+
+ if (*str == '[') {
+ /* IPv6, [a:b:c...h]:p or [*]:p */
+ str++;
+ tmp = strchr(str, ']');
+ if (!tmp)
+ goto out;
+ *tmp = '\0';
+
+ addr = zmalloc(sizeof(*addr));
+ if (!addr)
+ goto out;
+ list_add(&addr->list, list);
+
+ if (!strcmp(str, "*"))
+ addr->in6.sin6_addr = in6addr_any;
+ else if (inet_pton(AF_INET6, str, &addr->in6.sin6_addr) <= 0)
+ goto out;
+
+ tmp++;
+ if (*tmp != ':')
+ goto out;
+
+ tmp++;
+ if (strtou16_strict(tmp, &port) < 0)
+ goto out;
+
+ addr->in6.sin6_family = AF_INET6;
+ addr->in6.sin6_port = htons(port);
+ addr->addrlen = sizeof(addr->in6);
+
+ } else if (*str == '*') {
+ /* IPv4, *:p */
+ str++;
+ if (*str != ':')
+ goto out;
+
+ str++;
+ if (strtou16_strict(str, &port) < 0)
+ goto out;
+
+ addr = zmalloc(sizeof(*addr));
+ if (!addr)
+ goto out;
+ list_add(&addr->list, list);
+
+ addr->in4.sin_family = AF_INET;
+ addr->in4.sin_addr.s_addr = INADDR_ANY;
+ addr->in4.sin_port = htons(port);
+ addr->addrlen = sizeof(addr->in4);
+
+ } else if ((tmp = strchr(str, ':'))) {
+ /* IPv4, a.b.c.d:p or IPv4/6 hostname:p */
+ fprintf(stderr, "Got an IPv4:port or hostname:port\n");
+ *tmp = '\0';
+ tmp++;
+ if (strtou16_strict(tmp, &port) < 0)
+ goto out;
+
+ addr = zmalloc(sizeof(*addr));
+ if (!addr)
+ goto out;
+
+ if (inet_pton(AF_INET, str, &addr->in4.sin_addr) > 0) {
+ fprintf(stderr, "...Got an IPv4:port (0x%p, list 0x%p)\n", addr, &addr->list);
+ addr->in4.sin_family = AF_INET;
+ addr->in4.sin_port = htons(port);
+ addr->addrlen = sizeof(addr->in4);
+ list_add(&addr->list, list);
+ goto success;
+ } else {
+ free(addr);
+ }
+
+ struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_STREAM,
+ .ai_protocol = 0,
+ .ai_flags = 0,
+ };
+ struct addrinfo *results, *ai;
+
+ /* FIXME: This is completely synchronous but getaddrinfo_a is not very ergonomic */
+ r = getaddrinfo(str, tmp, &hints, &results);
+ if (r != 0) {
+ fprintf(stderr, "gettaddrinfo(%s): %s\n", str, gai_strerror(r));
+ goto out;
+ }
+
+ fprintf(stderr, "...Got an hostname:port\n");
+ for (ai = results; ai; ai = ai->ai_next) {
+ fprintf(stderr, "Got a result from getaddrinfo\n");
+
+ addr = zmalloc(sizeof(*addr));
+ if (!addr) {
+ freeaddrinfo(results);
+ goto out;
+ }
+
+ switch (ai->ai_family) {
+ case AF_INET: {
+ struct sockaddr_in *naddr = (struct sockaddr_in *)ai->ai_addr;
+
+ addr->in4.sin_family = AF_INET;
+ addr->in4.sin_addr = naddr->sin_addr;
+ addr->in4.sin_port = naddr->sin_port;
+ addr->addrlen = sizeof(addr->in4);
+ list_add(&addr->list, list);
+ break;
+ }
+ case AF_INET6: {
+ struct sockaddr_in6 *naddr = (struct sockaddr_in6 *)ai->ai_addr;
+
+ addr->in6.sin6_family = AF_INET6;
+ addr->in6.sin6_addr = naddr->sin6_addr;
+ addr->in6.sin6_port = naddr->sin6_port;
+ addr->addrlen = sizeof(addr->in6);
+ list_add(&addr->list, list);
+ break;
+ }
+ default:
+ fprintf(stderr, " Fam: Unknown (%i)\n", ai->ai_family);
+ free(addr);
+ continue;
+ }
+ }
+
+ freeaddrinfo(results);
+
+ } else if (strtou16_strict(tmp, &port) == 0) {
+ /* Port */
+
+ addr = zmalloc(sizeof(*addr));
+ if (!addr)
+ goto out;
+ addr->in6.sin6_family = AF_INET6;
+ addr->in6.sin6_addr = in6addr_any;
+ addr->in6.sin6_port = htons(port);
+ addr->addrlen = sizeof(addr->in6);
+ list_add(&addr->list, list);
+
+ addr = zmalloc(sizeof(*addr));
+ if (!addr)
+ goto out;
+ addr->in4.sin_family = AF_INET;
+ addr->in4.sin_addr.s_addr = INADDR_ANY;
+ addr->in4.sin_port = htons(port);
+ addr->addrlen = sizeof(addr->in4);
+ list_add(&addr->list, list);
+ }
+
+success:
+ if (list_empty(list)) {
+ fprintf(stderr, "Success but empty list!?\n");
+ return false;
+ } else {
+ int i = 0;
+ struct list_head *pos;
+
+ list_for_each(pos, list)
+ i++;
+ fprintf(stderr, "Success, %i entries\n", i);
+ }
+
+ return true;
+
+out:
+ if (!list_empty(list)) {
+ struct sockaddr_in46 *tmpaddr;
+
+ list_for_each_entry_safe(addr, tmpaddr, list, list) {
+ list_del(&addr->list);
+ free(addr);
+ }
+ }
+ return false;
+}
+
+enum scfg_keys {
+ SCFG_KEY_TYPE,
+ SCFG_KEY_NAME,
+ SCFG_KEY_PORT,
+ SCFG_KEY_LOCAL,
+ SCFG_KEY_REMOTE,
+ SCFG_KEY_INVALID
+};
+
+enum value_type {
+ VAL_TYPE_STRING,
+ VAL_TYPE_UINT16,
+ VAL_TYPE_ADDR,
+ VAL_TYPE_INVALID
+};
+
+struct scfg_key_map {
+ const char *key_name;
+ enum scfg_keys key_value;
+ enum value_type value_type;
+} scfg_key_map[] = {
+ {
+ .key_name = "type",
+ .key_value = SCFG_KEY_TYPE,
+ .value_type = VAL_TYPE_STRING,
+ }, {
+ .key_name = "name",
+ .key_value = SCFG_KEY_NAME,
+ .value_type = VAL_TYPE_STRING,
+ }, {
+ .key_name = "port",
+ .key_value = SCFG_KEY_PORT,
+ .value_type = VAL_TYPE_UINT16,
+ }, {
+ .key_name = "local",
+ .key_value = SCFG_KEY_LOCAL,
+ .value_type = VAL_TYPE_ADDR,
+ }, {
+ .key_name = "remote",
+ .key_value = SCFG_KEY_REMOTE,
+ .value_type = VAL_TYPE_ADDR,
+ }
+};
+
+union cfg_value {
+ const char *str;
+ uint16_t uint16;
+ struct list_head addr_list;
+};
+
+
+static void
+line_get_key_value(char *line, enum scfg_keys *rkey, union cfg_value *rvalue)
+{
+ char *tmp, *key;
+ int i;
+
+ *rkey = SCFG_KEY_INVALID;
+ if (!line)
+ return;
+
+ tmp = line;
+ while (isspace(*tmp))
+ tmp++;
+
+ if (*tmp == '\0')
+ return;
+
+ key = tmp;
+ while (*tmp != '\0' && !isspace(*tmp))
+ tmp++;
+
+ if (*tmp == '\0')
+ return;
+
+ *tmp = '\0';
+ tmp++;
+
+ while (isspace(*tmp))
+ tmp++;
+
+ if (*tmp != '=')
+ return;
+
+ tmp++;
+ while (isspace(*tmp))
+ tmp++;
+
+ if (*tmp == '\0')
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(scfg_key_map); i++) {
+ if (strcmp(scfg_key_map[i].key_name, key))
+ continue;
+
+ switch (scfg_key_map[i].value_type) {
+
+ case VAL_TYPE_STRING:
+ rvalue->str = tmp;
+ break;
+
+ case VAL_TYPE_UINT16: {
+ uint16_t v;
+
+ if (strtou16_strict(tmp, &v) < 0)
+ return;
+ rvalue->uint16 = v;
+ break;
+ }
+
+ case VAL_TYPE_ADDR: {
+ if (!strtosockaddrs(tmp, &rvalue->addr_list))
+ return;
+ if (list_empty(&rvalue->addr_list)) {
+ fprintf(stderr, "VAL_TYPE_ADDR with zero list!?\n");
+ return;
+ } else {
+ int i = 0;
+ struct list_head *pos;
+
+ list_for_each(pos, &rvalue->addr_list)
+ i++;
+ fprintf(stderr, "VAL_TYPE_ADDR with list %i entries\n", i);
+ }
+ break;
+ }
+
+ case VAL_TYPE_INVALID:
+ /* fall through */
+ default:
+ return;
+ }
+ *rkey = scfg_key_map[i].key_value;
+ break;
+ }
+}
+
+static void
+scfg_parse(struct cfg *cfg, struct server *scfg)
+{
+ char *pos = &scfg->buf[0];
+ char *line;
+
+ eat_whitespace_and_comments(&pos);
+
+ line = get_line(&pos);
+ if (!line) {
+ printf("Cfg: premature EOF\n");
+ return;
+ }
+
+ if (strcmp(line, "[server]")) {
+ printf("Invalid line: %s\n", line);
+ return;
+ }
+
+ while (true) {
+ enum scfg_keys key;
+ union cfg_value value;
+
+ eat_whitespace_and_comments(&pos);
+ line = get_line(&pos);
+ printf("Examining line: %s\n", line);
+ line_get_key_value(line, &key, &value);
+ if (key == SCFG_KEY_INVALID)
+ break;
+ printf("Got a key-value pair: %i = something\n", key);
+
+ switch (key) {
+
+ case SCFG_KEY_TYPE:
+ if (!strcmp(value.str, "proxy")) {
+ if (!server_set_type(cfg, scfg, SERVER_TYPE_PROXY))
+ return;
+ } else if (!strcmp(value.str, "announce")) {
+ if (!server_set_type(cfg, scfg, SERVER_TYPE_ANNOUNCE))
+ return;
+ }
+ break;
+
+ case SCFG_KEY_NAME:
+ if (!server_set_pretty_name(cfg, scfg, value.str))
+ return;
+ break;
+
+ case SCFG_KEY_PORT:
+ if (!server_set_port(cfg, scfg, value.uint16))
+ return;
+ break;
+
+ case SCFG_KEY_LOCAL: {
+ struct sockaddr_in46 *addr, *tmp;
+
+ list_for_each_entry_safe(addr, tmp, &value.addr_list, list) {
+ list_del(&addr->list);
+ server_add_local(cfg, scfg, addr);
+ }
+ break;
+ }
+
+ case SCFG_KEY_REMOTE: {
+ struct sockaddr_in46 *addr, *tmp;
+
+ list_for_each_entry_safe(addr, tmp, &value.addr_list, list) {
+ list_del(&addr->list);
+ server_add_remote(cfg, scfg, addr);
+ }
+ break;
+ }
+
+ case SCFG_KEY_INVALID:
+ default:
+ break;
+ }
+ }
+
+ //printf("Cfg:\n%s\n\n", pos);
+}
+
+static void
+scfg_read_cb(struct cfg *cfg, struct uring_task *task, int res)
+{
+ struct server *scfg = container_of(task, struct server, task);
+
+ printf("Asked to parse server cfg %s (bytes %i)\n", scfg->name, res);
+
+ if (res < 0) {
+ perrordie("read");
+ } else if (res > 0) {
+ scfg->len += res;
+ if (scfg->len + 1 >= sizeof(scfg->buf)) {
+ fprintf(stderr, "Server config too large\n");
+ server_delete(cfg, scfg);
+ return;
+ }
+
+ uring_read(cfg, &scfg->task, scfg->buf + scfg->len, sizeof(scfg->buf) - scfg->len, scfg->len, scfg_read_cb);
+ return;
+ } else {
+ /* EOF */
+ scfg->buf[scfg->len] = '\0';
+ uring_task_close_fd(cfg, &scfg->task);
+ scfg_parse(cfg, scfg);
+ server_commit(cfg, scfg);
+ }
+}
+
+static void
+scfg_open_cb(struct cfg *cfg, struct uring_task *task, int res)
+{
+ struct server *scfg = container_of(task, struct server, task);
+
+ if (res < 0) {
+ fprintf(stderr, "Open failed\n");
+ server_delete(cfg, scfg);
+ return;
+ }
+
+ printf("Asked to read server cfg %s (fd %i)\n", scfg->name, res);
+ uring_task_set_fd(&scfg->task, res);
+ scfg->len = 0;
+ uring_read(cfg, &scfg->task, scfg->buf, sizeof(scfg->buf), 0, scfg_read_cb);
+}
+
+static bool
+scfg_valid_filename(const char *name)
+{
+ const char *suffix;
+
+ if (!name)
+ return false;
+ if (name[0] == '\0')
+ return false;
+ if (name[0] == '.')
+ return false;
+ if ((suffix = strrchr(name, '.')) == NULL)
+ return false;
+ if (strcmp(suffix, ".server"))
+ return false;
+
+ return true;
+}
+
+struct inotify_ev {
+ struct uring_task task;
+ char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event))));
+};
+
+static void
+inotify_free(struct uring_task *task)
+{
+ struct inotify_ev *iev = container_of(task, struct inotify_ev, task);
+ struct cfg *cfg = container_of(task->parent, struct cfg, task);
+
+ fprintf(stderr, "%s called\n", __func__);
+ if (!iev || !cfg)
+ die("%s: iev or cfg is NULL!?\n", __func__);
+
+ free(iev);
+ cfg->iev = NULL;
+ uring_task_put(cfg, &cfg->task);
+}
+
+static void
+inotify_event_dump(const struct inotify_event *event)
+{
+ printf("Event:\n");
+ printf(" * WD : %i\n", event->wd);
+ printf(" * Cookie : %" PRIu32 "\n", event->cookie);
+ printf(" * Length : %" PRIu32 "\n", event->len);
+ printf(" * Name : %s\n", event->name);
+ printf(" * Mask : %" PRIu32 "\n", event->mask);
+ if (event->mask & IN_ACCESS)
+ printf("\tIN_ACCESS\n");
+ else if(event->mask & IN_MODIFY)
+ printf("\tIN_MODIFY\n");
+ else if(event->mask & IN_ATTRIB)
+ printf("\tIN_ATTRIB\n");
+ else if(event->mask & IN_CLOSE_WRITE)
+ printf("\tIN_CLOSE_WRITE\n");
+ else if(event->mask & IN_CLOSE_NOWRITE)
+ printf("\tIN_CLOSE_NOWRITE\n");
+ else if(event->mask & IN_OPEN)
+ printf("\tIN_OPEN\n");
+ else if(event->mask & IN_MOVED_FROM)
+ printf("\tIN_MOVED_FROM\n");
+ else if(event->mask & IN_MOVED_TO)
+ printf("\tIN_MOVED_TO\n");
+ else if(event->mask & IN_CREATE)
+ printf("\tIN_CREATE\n");
+ else if(event->mask & IN_DELETE)
+ printf("\tIN_DELETE\n");
+ else if(event->mask & IN_DELETE_SELF)
+ printf("\tIN_DELETE_SELF\n");
+ else if(event->mask & IN_MOVE_SELF)
+ printf("\tIN_MOVE_SELF\n");
+ else if(event->mask & IN_UNMOUNT)
+ printf("\tIN_UNMOUNT\n");
+ else if(event->mask & IN_Q_OVERFLOW)
+ printf("\tIN_Q_OVERFLOW\n");
+ else if(event->mask & IN_IGNORED)
+ printf("\tIN_IGNORED\n");
+ printf("\n");
+}
+
+static void
+inotify_cb(struct cfg *cfg, struct uring_task *task, int res)
+{
+ struct inotify_ev *iev = container_of(task, struct inotify_ev, task);
+ const struct inotify_event *event;
+ char *ptr;
+ struct server *scfg;
+
+ fprintf(stderr, "%s: ret is %i (ref %u)\n", __func__, res, task->refcount);
+
+ if (task->dead) {
+ fprintf(stderr, "%s: task is dead\n", __func__);
+ uring_task_put(cfg, task);
+ return;
+ }
+
+ if (res <= 0)
+ perrordie("inotify_read");
+
+ for (ptr = iev->buf; ptr < iev->buf + res; ptr += sizeof(struct inotify_event) + event->len) {
+ event = (const struct inotify_event *)ptr;
+
+ if (debuglvl > 0)
+ inotify_event_dump(event);
+
+ if (event->mask & (IN_IGNORED | IN_MOVE_SELF | IN_DELETE_SELF | IN_UNMOUNT))
+ die("Configuration directory gone, exiting\n");
+
+ if (event->mask & IN_Q_OVERFLOW) {
+ error("inotify queue overflow!\n");
+ continue;
+ }
+
+ if (!scfg_valid_filename(event->name))
+ continue;
+
+ if (event->mask & (IN_MOVED_FROM | IN_DELETE))
+ server_delete_by_name(cfg, event->name);
+ else if (event->mask & (IN_MOVED_TO | IN_CREATE | IN_CLOSE_WRITE)) {
+ scfg = server_new(cfg, event->name);
+ uring_openat(cfg, &scfg->task, event->name, scfg_open_cb);
+ } else
+ error("inotify: weird, unknown event: 0x%08x\n", event->mask);
+ }
+
+ uring_read(cfg, &iev->task, iev->buf, sizeof(iev->buf), 0, inotify_cb);
+}
+
+void
+inotify_refdump(struct inotify_ev *iev)
+{
+ uring_task_refdump(&iev->task);
+}
+
+void
+scfg_stop_monitor_dir(struct cfg *cfg)
+{
+ if (!cfg->iev) {
+ fprintf(stderr, "%s called with no iev!\n", __func__);
+ return;
+ }
+
+ fprintf(stderr, "%s called, closing fd %i\n", __func__, cfg->iev->task.fd);
+ uring_cancel(cfg, &cfg->iev->task);
+ cfg->iev = NULL;
+}
+
+void
+scfg_monitor_dir(struct cfg *cfg)
+{
+ int ifd;
+ int iwd;
+ struct inotify_ev *iev;
+
+ iev = malloc(sizeof(*iev));
+ if (!iev)
+ perrordie("malloc");
+
+ ifd = inotify_init1(IN_CLOEXEC);
+ if (ifd < 0)
+ perrordie("inotify_init1");
+
+ /* ln = IN_CREATE, cp/vi/mv = IN_CREATE, IN_OPEN, IN_CLOSE_WRITE */
+ iwd = inotify_add_watch(ifd, ".",
+ IN_CLOSE_WRITE | IN_DELETE | IN_CREATE |
+ IN_DELETE_SELF | IN_MOVE_SELF | IN_MOVED_TO |
+ IN_MOVED_FROM | IN_DONT_FOLLOW |
+ IN_EXCL_UNLINK | IN_ONLYDIR );
+ if (iwd < 0)
+ perrordie("inotify_add_watch");
+
+ uring_task_init(&iev->task, "iev", &cfg->task, inotify_free);
+ uring_task_set_fd(&iev->task, ifd);
+ cfg->iev = iev;
+ uring_read(cfg, &iev->task, iev->buf, sizeof(iev->buf), 0, inotify_cb);
+}
+
+void
+scfg_read_all(struct cfg *cfg)
+{
+
+ DIR *cfgdir;
+ struct dirent *dent;
+ struct server *scfg;
+
+ cfgdir = opendir(".");
+ if (!cfgdir) {
+ perror("opendir");
+ free(cfg);
+ return;
+ }
+
+ while ((dent = readdir(cfgdir)) != NULL) {
+ char *suffix;
+
+ if (dent->d_name[0] == '.')
+ continue;
+ if (dent->d_type != DT_REG && dent->d_type != DT_UNKNOWN)
+ continue;
+ if ((suffix = strrchr(dent->d_name, '.')) == NULL)
+ continue;
+ if (strcmp(suffix, ".server"))
+ continue;
+
+ scfg = server_new(cfg, dent->d_name);
+ uring_openat(cfg, &scfg->task, dent->d_name, scfg_open_cb);
+ }
+
+ closedir(cfgdir);
+}
+
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..c70c6ef
--- /dev/null
+++ b/config.h
@@ -0,0 +1,12 @@
+#ifndef fooconfighfoo
+#define fooconfighfoo
+
+void scfg_monitor_dir(struct cfg *cfg);
+
+void inotify_refdump(struct inotify_ev *iev);
+
+void scfg_stop_monitor_dir(struct cfg *cfg);
+
+void scfg_read_all(struct cfg *cfg);
+
+#endif
diff --git a/ctest.c b/ctest.c
new file mode 100644
index 0000000..2e871cb
--- /dev/null
+++ b/ctest.c
@@ -0,0 +1,74 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <string.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdbool.h>
+
+#define PIPE_RD 0
+#define PIPE_WR 1
+
+int
+main(int argc, char **argv) {
+ int sfd;
+ struct sockaddr_in addr;
+ int zfd;
+ int pfd[2];
+ size_t total = 0;
+
+ if (argc != 4) {
+ fprintf(stderr, "Usage: %s <addr> <port> <64k count>\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ sfd = socket(AF_INET, SOCK_STREAM, 0);
+ if (sfd < 0) {
+ perror("socket");
+ exit(EXIT_FAILURE);
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(atoi(argv[2]));
+ addr.sin_addr.s_addr = inet_addr(argv[1]);
+
+ if (connect(sfd, &addr, sizeof(addr)) < 0) {
+ perror("connect");
+ exit(EXIT_FAILURE);
+ }
+
+ zfd = open("/dev/zero", O_RDONLY | O_CLOEXEC);
+ if (zfd < 0) {
+ perror("open");
+ exit(EXIT_FAILURE);
+ }
+
+ if (pipe2(pfd, O_CLOEXEC) < 0) {
+ perror("pipe2");
+ exit(EXIT_FAILURE);
+ }
+
+ for (int i = 0; i < atoi(argv[3]); i++) {
+ ssize_t r, w;
+
+ r = splice(zfd, NULL, pfd[PIPE_WR], NULL, 64 * 1024, SPLICE_F_MOVE);
+ //fprintf(stderr, "Read %zi bytes from /dev/zero\n", r);
+ w = splice(pfd[PIPE_RD], NULL, sfd, NULL, r, SPLICE_F_MOVE);
+ //fprintf(stderr, "Wrote %zi bytes to socket\n", w);
+ if (r != w) {
+ fprintf(stderr, "Read/write mismatch\n");
+ exit(EXIT_FAILURE);
+ }
+ total += w;
+ }
+
+ printf("Client: sent %zu bytes\n", total);
+ exit(EXIT_SUCCESS);
+}
+
diff --git a/inotify.c b/inotify.c
new file mode 100644
index 0000000..f362920
--- /dev/null
+++ b/inotify.c
@@ -0,0 +1,730 @@
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdbool.h>
+#include <sys/inotify.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <inttypes.h>
+
+#include "main.h"
+#include "uring.h"
+#include "config.h"
+#include "server.h"
+
+static void
+eat_whitespace_and_comments(char **pos)
+{
+ while (true) {
+ while (isspace(**pos))
+ (*pos)++;
+
+ if (**pos == '#') {
+ while (**pos != '\r' && **pos != '\n' && **pos != '\0')
+ (*pos)++;
+ continue;
+ }
+
+ return;
+ }
+}
+
+static char *
+get_line(char **pos)
+{
+ char *begin = *pos;
+ char *end;
+
+ while (isspace(*begin))
+ begin++;
+
+ if (*begin == '\0')
+ return NULL;
+
+ end = begin;
+ while (*end != '\n' && *end != '\0')
+ end++;
+
+ if (*end == '\0')
+ *pos = end;
+ else
+ *pos = end + 1;
+
+ while (isspace(*end)) {
+ *end = '\0';
+ end--;
+ }
+
+ return begin;
+}
+
+static bool
+strtosockaddrs(const char *str, struct list_head *list)
+{
+ char *tmp;
+ uint16_t port;
+ int r;
+ struct sockaddr_in46 *addr;
+
+ if (!str || *str == '\0' || !list)
+ return false;
+ list_init(list);
+
+ if (*str == '[') {
+ /* IPv6, [a:b:c...h]:p or [*]:p */
+ str++;
+ tmp = strchr(str, ']');
+ if (!tmp)
+ goto out;
+ *tmp = '\0';
+
+ addr = zmalloc(sizeof(*addr));
+ if (!addr)
+ goto out;
+ list_add(&addr->list, list);
+
+ if (!strcmp(str, "*"))
+ addr->in6.sin6_addr = in6addr_any;
+ else if (inet_pton(AF_INET6, str, &addr->in6.sin6_addr) <= 0)
+ goto out;
+
+ tmp++;
+ if (*tmp != ':')
+ goto out;
+
+ tmp++;
+ if (strtou16_strict(tmp, &port) < 0)
+ goto out;
+
+ addr->in6.sin6_family = AF_INET6;
+ addr->in6.sin6_port = htons(port);
+ addr->addrlen = sizeof(addr->in6);
+
+ } else if (*str == '*') {
+ /* IPv4, *:p */
+ str++;
+ if (*str != ':')
+ goto out;
+
+ str++;
+ if (strtou16_strict(str, &port) < 0)
+ goto out;
+
+ addr = zmalloc(sizeof(*addr));
+ if (!addr)
+ goto out;
+ list_add(&addr->list, list);
+
+ addr->in4.sin_family = AF_INET;
+ addr->in4.sin_addr.s_addr = INADDR_ANY;
+ addr->in4.sin_port = htons(port);
+ addr->addrlen = sizeof(addr->in4);
+
+ } else if ((tmp = strchr(str, ':'))) {
+ /* IPv4, a.b.c.d:p or IPv4/6 hostname:p */
+ fprintf(stderr, "Got an IPv4:port or hostname:port\n");
+ *tmp = '\0';
+ tmp++;
+ if (strtou16_strict(tmp, &port) < 0)
+ goto out;
+
+ addr = zmalloc(sizeof(*addr));
+ if (!addr)
+ goto out;
+
+ if (inet_pton(AF_INET, str, &addr->in4.sin_addr) > 0) {
+ fprintf(stderr, "...Got an IPv4:port (0x%p, list 0x%p)\n", addr, &addr->list);
+ addr->in4.sin_family = AF_INET;
+ addr->in4.sin_port = htons(port);
+ addr->addrlen = sizeof(addr->in4);
+ list_add(&addr->list, list);
+ goto success;
+ } else {
+ free(addr);
+ }
+
+ struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_STREAM,
+ .ai_protocol = 0,
+ .ai_flags = 0,
+ };
+ struct addrinfo *results, *ai;
+
+ /* FIXME: This is completely synchronous but getaddrinfo_a is not very ergonomic */
+ r = getaddrinfo(str, tmp, &hints, &results);
+ if (r != 0) {
+ fprintf(stderr, "gettaddrinfo(%s): %s\n", str, gai_strerror(r));
+ goto out;
+ }
+
+ fprintf(stderr, "...Got an hostname:port\n");
+ for (ai = results; ai; ai = ai->ai_next) {
+ fprintf(stderr, "Got a result from getaddrinfo\n");
+
+ addr = zmalloc(sizeof(*addr));
+ if (!addr) {
+ freeaddrinfo(results);
+ goto out;
+ }
+
+ switch (ai->ai_family) {
+ case AF_INET: {
+ struct sockaddr_in *naddr = (struct sockaddr_in *)ai->ai_addr;
+
+ addr->in4.sin_family = AF_INET;
+ addr->in4.sin_addr = naddr->sin_addr;
+ addr->in4.sin_port = naddr->sin_port;
+ addr->addrlen = sizeof(addr->in4);
+ list_add(&addr->list, list);
+ break;
+ }
+ case AF_INET6: {
+ struct sockaddr_in6 *naddr = (struct sockaddr_in6 *)ai->ai_addr;
+
+ addr->in6.sin6_family = AF_INET6;
+ addr->in6.sin6_addr = naddr->sin6_addr;
+ addr->in6.sin6_port = naddr->sin6_port;
+ addr->addrlen = sizeof(addr->in6);
+ list_add(&addr->list, list);
+ break;
+ }
+ default:
+ fprintf(stderr, " Fam: Unknown (%i)\n", ai->ai_family);
+ free(addr);
+ continue;
+ }
+ }
+
+ freeaddrinfo(results);
+
+ } else if (strtou16_strict(tmp, &port) == 0) {
+ /* Port */
+
+ addr = zmalloc(sizeof(*addr));
+ if (!addr)
+ goto out;
+ addr->in6.sin6_family = AF_INET6;
+ addr->in6.sin6_addr = in6addr_any;
+ addr->in6.sin6_port = htons(port);
+ addr->addrlen = sizeof(addr->in6);
+ list_add(&addr->list, list);
+
+ addr = zmalloc(sizeof(*addr));
+ if (!addr)
+ goto out;
+ addr->in4.sin_family = AF_INET;
+ addr->in4.sin_addr.s_addr = INADDR_ANY;
+ addr->in4.sin_port = htons(port);
+ addr->addrlen = sizeof(addr->in4);
+ list_add(&addr->list, list);
+ }
+
+success:
+ if (list_empty(list)) {
+ fprintf(stderr, "Success but empty list!?\n");
+ return false;
+ } else {
+ int i = 0;
+ struct list_head *pos;
+
+ list_for_each(pos, list)
+ i++;
+ fprintf(stderr, "Success, %i entries\n", i);
+ }
+
+ return true;
+
+out:
+ if (!list_empty(list)) {
+ struct sockaddr_in46 *tmpaddr;
+
+ list_for_each_entry_safe(addr, tmpaddr, list, list) {
+ list_del(&addr->list);
+ free(addr);
+ }
+ }
+ return false;
+}
+
+enum scfg_keys {
+ SCFG_KEY_TYPE,
+ SCFG_KEY_NAME,
+ SCFG_KEY_PORT,
+ SCFG_KEY_LOCAL,
+ SCFG_KEY_REMOTE,
+ SCFG_KEY_INVALID
+};
+
+enum value_type {
+ VAL_TYPE_STRING,
+ VAL_TYPE_UINT16,
+ VAL_TYPE_ADDR,
+ VAL_TYPE_INVALID
+};
+
+struct scfg_key_map {
+ const char *key_name;
+ enum scfg_keys key_value;
+ enum value_type value_type;
+} scfg_key_map[] = {
+ {
+ .key_name = "type",
+ .key_value = SCFG_KEY_TYPE,
+ .value_type = VAL_TYPE_STRING,
+ }, {
+ .key_name = "name",
+ .key_value = SCFG_KEY_NAME,
+ .value_type = VAL_TYPE_STRING,
+ }, {
+ .key_name = "port",
+ .key_value = SCFG_KEY_PORT,
+ .value_type = VAL_TYPE_UINT16,
+ }, {
+ .key_name = "local",
+ .key_value = SCFG_KEY_LOCAL,
+ .value_type = VAL_TYPE_ADDR,
+ }, {
+ .key_name = "remote",
+ .key_value = SCFG_KEY_REMOTE,
+ .value_type = VAL_TYPE_ADDR,
+ }
+};
+
+union cfg_value {
+ const char *str;
+ uint16_t uint16;
+ struct list_head addr_list;
+};
+
+
+static void
+line_get_key_value(char *line, enum scfg_keys *rkey, union cfg_value *rvalue)
+{
+ char *tmp, *key;
+ int i;
+
+ *rkey = SCFG_KEY_INVALID;
+ if (!line)
+ return;
+
+ tmp = line;
+ while (isspace(*tmp))
+ tmp++;
+
+ if (*tmp == '\0')
+ return;
+
+ key = tmp;
+ while (*tmp != '\0' && !isspace(*tmp))
+ tmp++;
+
+ if (*tmp == '\0')
+ return;
+
+ *tmp = '\0';
+ tmp++;
+
+ while (isspace(*tmp))
+ tmp++;
+
+ if (*tmp != '=')
+ return;
+
+ tmp++;
+ while (isspace(*tmp))
+ tmp++;
+
+ if (*tmp == '\0')
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(scfg_key_map); i++) {
+ if (strcmp(scfg_key_map[i].key_name, key))
+ continue;
+
+ switch (scfg_key_map[i].value_type) {
+
+ case VAL_TYPE_STRING:
+ rvalue->str = tmp;
+ break;
+
+ case VAL_TYPE_UINT16: {
+ uint16_t v;
+
+ if (strtou16_strict(tmp, &v) < 0)
+ return;
+ rvalue->uint16 = v;
+ break;
+ }
+
+ case VAL_TYPE_ADDR: {
+ if (!strtosockaddrs(tmp, &rvalue->addr_list))
+ return;
+ if (list_empty(&rvalue->addr_list)) {
+ fprintf(stderr, "VAL_TYPE_ADDR with zero list!?\n");
+ return;
+ } else {
+ int i = 0;
+ struct list_head *pos;
+
+ list_for_each(pos, &rvalue->addr_list)
+ i++;
+ fprintf(stderr, "VAL_TYPE_ADDR with list %i entries\n", i);
+ }
+ break;
+ }
+
+ case VAL_TYPE_INVALID:
+ /* fall through */
+ default:
+ return;
+ }
+ *rkey = scfg_key_map[i].key_value;
+ break;
+ }
+}
+
+static void
+scfg_parse(struct cfg *cfg, struct server *scfg)
+{
+ char *pos = &scfg->buf[0];
+ char *line;
+
+ eat_whitespace_and_comments(&pos);
+
+ line = get_line(&pos);
+ if (!line) {
+ printf("Cfg: premature EOF\n");
+ return;
+ }
+
+ if (strcmp(line, "[server]")) {
+ printf("Invalid line: %s\n", line);
+ return;
+ }
+
+ while (true) {
+ enum scfg_keys key;
+ union cfg_value value;
+
+ eat_whitespace_and_comments(&pos);
+ line = get_line(&pos);
+ printf("Examining line: %s\n", line);
+ line_get_key_value(line, &key, &value);
+ if (key == SCFG_KEY_INVALID)
+ break;
+ printf("Got a key-value pair: %i = something\n", key);
+
+ switch (key) {
+
+ case SCFG_KEY_TYPE:
+ if (!strcmp(value.str, "proxy")) {
+ if (!server_set_type(cfg, scfg, SERVER_TYPE_PROXY))
+ return;
+ } else if (!strcmp(value.str, "announce")) {
+ if (!server_set_type(cfg, scfg, SERVER_TYPE_ANNOUNCE))
+ return;
+ }
+ break;
+
+ case SCFG_KEY_NAME:
+ if (!server_set_pretty_name(cfg, scfg, value.str))
+ return;
+ break;
+
+ case SCFG_KEY_PORT:
+ if (!server_set_port(cfg, scfg, value.uint16))
+ return;
+ break;
+
+ case SCFG_KEY_LOCAL: {
+ struct sockaddr_in46 *addr, *tmp;
+
+ list_for_each_entry_safe(addr, tmp, &value.addr_list, list) {
+ list_del(&addr->list);
+ server_add_local(cfg, scfg, addr);
+ }
+ break;
+ }
+
+ case SCFG_KEY_REMOTE: {
+ struct sockaddr_in46 *addr, *tmp;
+
+ list_for_each_entry_safe(addr, tmp, &value.addr_list, list) {
+ list_del(&addr->list);
+ server_add_remote(cfg, scfg, addr);
+ }
+ break;
+ }
+
+ case SCFG_KEY_INVALID:
+ default:
+ break;
+ }
+ }
+
+ //printf("Cfg:\n%s\n\n", pos);
+}
+
+static void
+scfg_read_cb(struct cfg *cfg, struct uring_task *task, int res)
+{
+ struct server *scfg = container_of(task, struct server, task);
+
+ printf("Asked to parse server cfg %s (bytes %i)\n", scfg->name, res);
+
+ if (res < 0) {
+ perrordie("read");
+ } else if (res > 0) {
+ scfg->len += res;
+ if (scfg->len + 1 >= sizeof(scfg->buf)) {
+ fprintf(stderr, "Server config too large\n");
+ server_delete(cfg, scfg);
+ return;
+ }
+
+ uring_read(cfg, &scfg->task, scfg->buf + scfg->len, sizeof(scfg->buf) - scfg->len, scfg->len, scfg_read_cb);
+ return;
+ } else {
+ /* EOF */
+ scfg->buf[scfg->len] = '\0';
+ uring_task_close_fd(cfg, &scfg->task);
+ scfg_parse(cfg, scfg);
+ server_commit(cfg, scfg);
+ }
+}
+
+static void
+scfg_open_cb(struct cfg *cfg, struct uring_task *task, int res)
+{
+ struct server *scfg = container_of(task, struct server, task);
+
+ if (res < 0) {
+ fprintf(stderr, "Open failed\n");
+ server_delete(cfg, scfg);
+ return;
+ }
+
+ printf("Asked to read server cfg %s (fd %i)\n", scfg->name, res);
+ uring_task_set_fd(&scfg->task, res);
+ scfg->len = 0;
+ uring_read(cfg, &scfg->task, scfg->buf, sizeof(scfg->buf), 0, scfg_read_cb);
+}
+
+static bool
+scfg_valid_filename(const char *name)
+{
+ const char *suffix;
+
+ if (!name)
+ return false;
+ if (name[0] == '\0')
+ return false;
+ if (name[0] == '.')
+ return false;
+ if ((suffix = strrchr(name, '.')) == NULL)
+ return false;
+ if (strcmp(suffix, ".server"))
+ return false;
+
+ return true;
+}
+
+struct inotify_ev {
+ struct uring_task task;
+ char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event))));
+};
+
+static void
+inotify_free(struct uring_task *task)
+{
+ struct inotify_ev *iev = container_of(task, struct inotify_ev, task);
+ struct cfg *cfg = container_of(task->parent, struct cfg, task);
+
+ fprintf(stderr, "%s called\n", __func__);
+ if (!iev || !cfg)
+ die("%s: iev or cfg is NULL!?\n", __func__);
+
+ free(iev);
+ cfg->iev = NULL;
+ uring_task_put(cfg, &cfg->task);
+}
+
+static void
+inotify_event_dump(const struct inotify_event *event)
+{
+ printf("Event:\n");
+ printf(" * WD : %i\n", event->wd);
+ printf(" * Cookie : %" PRIu32 "\n", event->cookie);
+ printf(" * Length : %" PRIu32 "\n", event->len);
+ printf(" * Name : %s\n", event->name);
+ printf(" * Mask : %" PRIu32 "\n", event->mask);
+ if (event->mask & IN_ACCESS)
+ printf("\tIN_ACCESS\n");
+ else if(event->mask & IN_MODIFY)
+ printf("\tIN_MODIFY\n");
+ else if(event->mask & IN_ATTRIB)
+ printf("\tIN_ATTRIB\n");
+ else if(event->mask & IN_CLOSE_WRITE)
+ printf("\tIN_CLOSE_WRITE\n");
+ else if(event->mask & IN_CLOSE_NOWRITE)
+ printf("\tIN_CLOSE_NOWRITE\n");
+ else if(event->mask & IN_OPEN)
+ printf("\tIN_OPEN\n");
+ else if(event->mask & IN_MOVED_FROM)
+ printf("\tIN_MOVED_FROM\n");
+ else if(event->mask & IN_MOVED_TO)
+ printf("\tIN_MOVED_TO\n");
+ else if(event->mask & IN_CREATE)
+ printf("\tIN_CREATE\n");
+ else if(event->mask & IN_DELETE)
+ printf("\tIN_DELETE\n");
+ else if(event->mask & IN_DELETE_SELF)
+ printf("\tIN_DELETE_SELF\n");
+ else if(event->mask & IN_MOVE_SELF)
+ printf("\tIN_MOVE_SELF\n");
+ else if(event->mask & IN_UNMOUNT)
+ printf("\tIN_UNMOUNT\n");
+ else if(event->mask & IN_Q_OVERFLOW)
+ printf("\tIN_Q_OVERFLOW\n");
+ else if(event->mask & IN_IGNORED)
+ printf("\tIN_IGNORED\n");
+ printf("\n");
+}
+
+static void
+inotify_cb(struct cfg *cfg, struct uring_task *task, int res)
+{
+ struct inotify_ev *iev = container_of(task, struct inotify_ev, task);
+ const struct inotify_event *event;
+ char *ptr;
+ struct server *scfg;
+
+ fprintf(stderr, "%s: ret is %i (ref %u)\n", __func__, res, task->refcount);
+
+ if (task->dead) {
+ fprintf(stderr, "%s: task is dead\n", __func__);
+ uring_task_put(cfg, task);
+ return;
+ }
+
+ if (res <= 0)
+ perrordie("inotify_read");
+
+ for (ptr = iev->buf; ptr < iev->buf + res; ptr += sizeof(struct inotify_event) + event->len) {
+ event = (const struct inotify_event *)ptr;
+
+ if (debuglvl > 0)
+ inotify_event_dump(event);
+
+ if (event->mask & (IN_IGNORED | IN_MOVE_SELF | IN_DELETE_SELF | IN_UNMOUNT))
+ die("Configuration directory gone, exiting\n");
+
+ if (event->mask & IN_Q_OVERFLOW) {
+ error("inotify queue overflow!\n");
+ continue;
+ }
+
+ if (!scfg_valid_filename(event->name))
+ continue;
+
+ if (event->mask & (IN_MOVED_FROM | IN_DELETE))
+ server_delete_by_name(cfg, event->name);
+ else if (event->mask & (IN_MOVED_TO | IN_CREATE | IN_CLOSE_WRITE)) {
+ scfg = server_new(cfg, event->name);
+ uring_openat(cfg, &scfg->task, event->name, scfg_open_cb);
+ } else
+ error("inotify: weird, unknown event: 0x%08x\n", event->mask);
+ }
+
+ uring_read(cfg, &iev->task, iev->buf, sizeof(iev->buf), 0, inotify_cb);
+}
+
+void
+inotify_refdump(struct inotify_ev *iev)
+{
+ uring_task_refdump(&iev->task);
+}
+
+void
+scfg_stop_monitor_dir(struct cfg *cfg)
+{
+ if (!cfg->iev) {
+ fprintf(stderr, "%s called with no iev!\n", __func__);
+ return;
+ }
+
+ fprintf(stderr, "%s called, closing fd %i\n", __func__, cfg->iev->task.fd);
+ uring_cancel(cfg, &cfg->iev->task);
+ cfg->iev = NULL;
+}
+
+void
+scfg_monitor_dir(struct cfg *cfg)
+{
+ int ifd;
+ int iwd;
+ struct inotify_ev *iev;
+
+ iev = malloc(sizeof(*iev));
+ if (!iev)
+ perrordie("malloc");
+
+ ifd = inotify_init1(IN_CLOEXEC);
+ if (ifd < 0)
+ perrordie("inotify_init1");
+
+ /* ln = IN_CREATE, cp/vi/mv = IN_CREATE, IN_OPEN, IN_CLOSE_WRITE */
+ iwd = inotify_add_watch(ifd, ".",
+ IN_CLOSE_WRITE | IN_DELETE | IN_CREATE |
+ IN_DELETE_SELF | IN_MOVE_SELF | IN_MOVED_TO |
+ IN_MOVED_FROM | IN_DONT_FOLLOW |
+ IN_EXCL_UNLINK | IN_ONLYDIR );
+ if (iwd < 0)
+ perrordie("inotify_add_watch");
+
+ uring_task_init(&iev->task, "iev", &cfg->task, inotify_free);
+ uring_task_set_fd(&iev->task, ifd);
+ cfg->iev = iev;
+ uring_read(cfg, &iev->task, iev->buf, sizeof(iev->buf), 0, inotify_cb);
+}
+
+void
+scfg_read_all(struct cfg *cfg)
+{
+
+ DIR *cfgdir;
+ struct dirent *dent;
+ struct server *scfg;
+
+ cfgdir = opendir(".");
+ if (!cfgdir) {
+ perror("opendir");
+ free(cfg);
+ return;
+ }
+
+ while ((dent = readdir(cfgdir)) != NULL) {
+ char *suffix;
+
+ if (dent->d_name[0] == '.')
+ continue;
+ if (dent->d_type != DT_REG && dent->d_type != DT_UNKNOWN)
+ continue;
+ if ((suffix = strrchr(dent->d_name, '.')) == NULL)
+ continue;
+ if (strcmp(suffix, ".server"))
+ continue;
+
+ scfg = server_new(cfg, dent->d_name);
+ uring_openat(cfg, &scfg->task, dent->d_name, scfg_open_cb);
+ }
+
+ closedir(cfgdir);
+}
+
diff --git a/inotify.h b/inotify.h
new file mode 100644
index 0000000..c70c6ef
--- /dev/null
+++ b/inotify.h
@@ -0,0 +1,12 @@
+#ifndef fooconfighfoo
+#define fooconfighfoo
+
+void scfg_monitor_dir(struct cfg *cfg);
+
+void inotify_refdump(struct inotify_ev *iev);
+
+void scfg_stop_monitor_dir(struct cfg *cfg);
+
+void scfg_read_all(struct cfg *cfg);
+
+#endif
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..70d1b1e
--- /dev/null
+++ b/main.c
@@ -0,0 +1,284 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <getopt.h>
+#include <sys/signalfd.h>
+#include <sys/eventfd.h>
+#include <signal.h>
+
+#include "main.h"
+#include "uring.h"
+#include "config.h"
+#include "server.h"
+
+int debuglvl = 0;
+
+void
+debug(unsigned lvl, const char *fmt, ...)
+{
+ va_list ap;
+
+ if (lvl > debuglvl)
+ return;
+
+ va_start(ap, fmt);
+ vfprintf(stdout, fmt, ap);
+ va_end(ap);
+}
+
+#define info(...) fprintf(stderr, __VA_ARGS__)
+#define error(...) fprintf(stderr, __VA_ARGS__)
+
+__attribute__((noreturn))
+void
+die(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ exit(EXIT_FAILURE);
+};
+
+#define perrordie(msg) die("%s: %m\n", msg)
+
+static void
+cfg_free(struct uring_task *task)
+{
+ struct cfg *cfg = container_of(task, struct cfg, task);
+
+ free(cfg);
+ exit(EXIT_SUCCESS);
+}
+
+static void
+cfg_read(struct cfg *cfg)
+{
+ FILE *cfgfile;
+
+ cfgfile = fopen("./mcproxy.conf", "r");
+ if (!cfgfile) {
+ if (errno == ENOENT)
+ return;
+ else
+ perrordie("fopen");
+ }
+
+ printf("Opened config file\n");
+ fclose(cfgfile);
+}
+
+static struct cfg *
+cfg_init(int argc, char **argv)
+{
+ struct cfg *cfg;
+ int c;
+
+ cfg = zmalloc(sizeof(*cfg));
+ if (!cfg) {
+ perror("malloc");
+ return NULL;
+ }
+ uring_task_init(&cfg->task, "cfg", NULL, cfg_free);
+ list_init(&cfg->servers);
+
+ while (true) {
+ int option_index = 0;
+ static struct option long_options[] = {
+ { "homedir", required_argument, 0, 'h' },
+ { "debug", no_argument, 0, 'd' },
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, ":h:d",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ cfg->homedir = optarg;
+ break;
+ case 'd':
+ debuglvl++;
+ break;
+ default:
+ die("Invalid arguments\n");
+ }
+
+ }
+
+ if (optind < argc)
+ die("Invalid arguments\n");
+
+ if (!cfg->homedir)
+ cfg->homedir = "/home/david/intest";
+
+ printf("Homedir is %s\n", cfg->homedir);
+ if (chdir(cfg->homedir))
+ perrordie("chdir");
+
+ return cfg;
+}
+
+struct signalfd_ev {
+ struct uring_task task;
+ //struct signalfd_siginfo buf;
+ uint64_t buf;
+};
+
+static void
+signalfd_free(struct uring_task *task)
+{
+ struct signalfd_ev *sev = container_of(task, struct signalfd_ev, task);
+
+ debug(2, "Freeing signalfd\n");
+ free(sev);
+}
+
+static void
+dump_tree(struct cfg *cfg)
+{
+ struct server *server;
+
+ fprintf(stderr, "\n\n\n\n");
+ fprintf(stderr, "Dumping Tree\n");
+ fprintf(stderr, "============\n");
+ uring_task_refdump(&cfg->task);
+ uring_task_refdump(&cfg->sev->task);
+ uring_refdump(cfg->uev);
+ if (cfg->iev)
+ inotify_refdump(cfg->iev);
+ list_for_each_entry(server, &cfg->servers, list)
+ server_refdump(server);
+ fprintf(stderr, "============\n");
+ fprintf(stderr, "\n\n\n\n");
+}
+
+static void
+signalfd_read(struct cfg *cfg, struct uring_task *task, int res)
+{
+ struct signalfd_ev *sev = container_of(task, struct signalfd_ev, task);
+ struct server *server, *stmp;
+ static int count = 0;
+
+ count++;
+ if (count > 5)
+ exit(EXIT_FAILURE);
+
+ if (res != sizeof(sev->buf))
+ die("Error in signalfd (%i)\n", res);
+
+ if (sev->buf < 1000) {
+ fprintf(stderr, "Got a signal to quit\n");
+ exit(EXIT_SUCCESS);
+ } else {
+ fprintf(stderr, "Got a signal to dump tree\n");
+ dump_tree(cfg);
+ scfg_stop_monitor_dir(cfg);
+ list_for_each_entry_safe(server, stmp, &cfg->servers, list)
+ server_delete(cfg, server);
+ uring_read(cfg, &sev->task, &sev->buf, sizeof(sev->buf), 0, signalfd_read);
+ }
+}
+
+static int hack_efd = -1;
+
+static void
+hack_handler(int signum)
+{
+ uint64_t val;
+
+ switch (signum) {
+ case SIGINT:
+ fprintf(stderr, "Got a SIGINT\n");
+ val = 1000;
+ break;
+ case SIGHUP:
+ fprintf(stderr, "Got a SIGHUP\n");
+ val = 1000;
+ break;
+ case SIGTERM:
+ fprintf(stderr, "Got a SIGTERM\n");
+ val = 1;
+ break;
+ default:
+ fprintf(stderr, "Got an unknown sig (%i)\n", signum);
+ val = 1;
+ break;
+ }
+
+ write(hack_efd, &val, sizeof(val));
+}
+
+static void
+signalfd_init(struct cfg *cfg)
+{
+ int sfd;
+ //sigset_t mask;
+ struct signalfd_ev *sev;
+
+ sev = zmalloc(sizeof(*sev));
+ if (!sev)
+ perrordie("malloc");
+
+ /*
+ sigfillset(&mask);
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0)
+ perrordie("sigprocmask");
+
+ sfd = signalfd(-1, &mask, SFD_CLOEXEC);
+ if (sfd < 0)
+ perrordie("signalfd");
+ */
+
+ struct sigaction action;
+ sigfillset(&action.sa_mask);
+ action.sa_handler = hack_handler;
+ action.sa_flags = 0;
+
+ sigaction(SIGINT, &action, NULL);
+ sigaction(SIGHUP, &action, NULL);
+ sigaction(SIGTERM, &action, NULL);
+
+ sfd = eventfd(0, EFD_CLOEXEC);
+ if (sfd < 0)
+ perrordie("eventfd");
+
+ fprintf(stderr, "signalfd init: %i\n", sfd);
+ uring_task_init(&sev->task, "sev", &cfg->task, signalfd_free);
+ uring_task_set_fd(&sev->task, sfd);
+ cfg->sev = sev;
+ hack_efd = sfd;
+ uring_read(cfg, &sev->task, &sev->buf, sizeof(sev->buf), 0, signalfd_read);
+}
+
+int
+main(int argc, char **argv)
+{
+ struct cfg *cfg;
+
+ cfg = cfg_init(argc, argv);
+
+ cfg_read(cfg);
+
+ uring_init(cfg);
+
+ signalfd_init(cfg);
+
+ scfg_monitor_dir(cfg);
+
+ scfg_read_all(cfg);
+
+ uring_event_loop(cfg);
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/main.h b/main.h
new file mode 100644
index 0000000..9ab769c
--- /dev/null
+++ b/main.h
@@ -0,0 +1,55 @@
+#ifndef foomainhfoo
+#define foomainhfoo
+
+#include <sys/socket.h>
+#include <netinet/ip.h>
+#include "utils.h"
+
+extern int debuglvl;
+
+void debug(unsigned lvl, const char *fmt, ...);
+
+#define info(...) fprintf(stderr, __VA_ARGS__)
+#define error(...) fprintf(stderr, __VA_ARGS__)
+
+void die(const char *fmt, ...);
+
+#define perrordie(msg) die("%s: %m\n", msg)
+
+struct cfg;
+
+struct uring_task;
+
+/* To save typing in all the function definitions below */
+typedef void (*callback_t)(struct cfg *, struct uring_task *, int res);
+
+struct uring_task {
+ const char *name;
+ unsigned refcount;
+ int fd;
+ void *parent;
+ void (*free)(struct uring_task *);
+ bool dead;
+ callback_t callback;
+};
+
+struct sockaddr_in46 {
+ union {
+ struct sockaddr_storage storage;
+ struct sockaddr_in in4;
+ struct sockaddr_in6 in6;
+ };
+ socklen_t addrlen;
+ struct list_head list;
+};
+
+struct cfg {
+ const char *homedir;
+ struct uring_ev *uev;
+ struct inotify_ev *iev;
+ struct signalfd_ev *sev;
+ struct uring_task task;
+ struct list_head servers;
+};
+
+#endif
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..afc5f0d
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,9 @@
+project('mcproxy', 'c', default_options : ['c_std=gnu18'])
+uring = dependency('liburing')
+
+executable('ctest', 'ctest.c')
+executable('stest', 'stest.c')
+executable('mcproxy',
+ ['main.c', 'uring.c', 'server.c', 'config.c', 'utils.c'],
+ dependencies: uring)
+
diff --git a/server.c b/server.c
new file mode 100644
index 0000000..1045e43
--- /dev/null
+++ b/server.c
@@ -0,0 +1,423 @@
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include "main.h"
+#include "uring.h"
+#include "config.h"
+#include "server.h"
+
+#define ADDRSTRLEN (9 /*strlen("AF_INETX ")*/ + INET6_ADDRSTRLEN + 6 /*strlen(" 65535")*/ + 1)
+
+struct server_local {
+ struct sockaddr_in46 addr;
+ char addrstr[ADDRSTRLEN];
+ struct sockaddr_in46 peer;
+ char peerstr[ADDRSTRLEN];
+ struct uring_task task;
+ struct list_head list;
+};
+
+/* FIXME: This can be a plain sockaddr_in46 */
+struct server_remote {
+ struct sockaddr_in46 addr;
+ char addrstr[ADDRSTRLEN];
+ struct list_head list;
+};
+
+struct server_proxy {
+ struct sockaddr_in46 client;
+ char clientstr[ADDRSTRLEN];
+ struct sockaddr_in46 server;
+ char serverstr[ADDRSTRLEN];
+ struct uring_task task;
+ char buf[4096];
+ size_t len;
+ struct list_head list;
+};
+
+void
+server_refdump(struct server *server)
+{
+ struct server_local *local;
+ struct server_proxy *proxy;
+
+ uring_task_refdump(&server->task);
+ list_for_each_entry(local, &server->locals, list)
+ uring_task_refdump(&local->task);
+ list_for_each_entry(proxy, &server->proxys, list)
+ uring_task_refdump(&proxy->task);
+}
+
+static void
+server_free(struct uring_task *task)
+{
+ struct server *scfg = container_of(task, struct server, task);
+
+ fprintf(stderr, "Freeing scfg %s\n", scfg->name);
+ list_del(&scfg->list);
+ free(scfg->pretty_name);
+ free(scfg->name);
+ free(scfg);
+}
+
+void
+server_delete(struct cfg *cfg, struct server *scfg)
+{
+ struct server_local *local, *ltmp;
+ struct server_remote *remote, *rtmp;
+
+ fprintf(stderr, "Removing server cfg: %s\n", scfg->name);
+
+ list_for_each_entry_safe(remote, rtmp, &scfg->remotes, list) {
+ list_del(&remote->list);
+ free(remote);
+ }
+
+ list_for_each_entry_safe(local, ltmp, &scfg->locals, list) {
+ uring_cancel(cfg, &local->task);
+ }
+
+ uring_task_put(cfg, &scfg->task);
+}
+
+void
+server_delete_by_name(struct cfg *cfg, const char *name)
+{
+ struct server *scfg;
+
+ if (!cfg || !name || name[0] == '\0')
+ return;
+
+ list_for_each_entry(scfg, &cfg->servers, list) {
+ if (!strcmp(scfg->name, name)) {
+ server_delete(cfg, scfg);
+ return;
+ }
+ }
+}
+
+static char *
+server_print_addr(struct sockaddr_in46 *addr, char *buf, size_t buflen)
+{
+ char abuf[ADDRSTRLEN];
+
+ switch (addr->storage.ss_family) {
+ case AF_INET:
+ snprintf(buf, buflen, "AF_INET4 %s %u",
+ inet_ntop(addr->in4.sin_family, &addr->in4.sin_addr, abuf, sizeof(abuf)),
+ (unsigned)ntohs(addr->in4.sin_port));
+ break;
+ case AF_INET6:
+ snprintf(buf, buflen, "AF_INET6 %s %u",
+ inet_ntop(addr->in6.sin6_family, &addr->in6.sin6_addr, abuf, sizeof(abuf)),
+ (unsigned)ntohs(addr->in6.sin6_port));
+ break;
+ default:
+ snprintf(buf, buflen, "AF_UNKNOWN");
+ break;
+ }
+
+ return buf;
+}
+
+static void
+server_dump(struct server *scfg)
+{
+ struct server_local *local;
+ struct server_remote *remote;
+
+ fprintf(stderr, "Dumping server %s\n", scfg->name);
+ switch (scfg->type) {
+ case SERVER_TYPE_ANNOUNCE:
+ fprintf(stderr, " * Type: announce\n");
+ break;
+ case SERVER_TYPE_PROXY:
+ fprintf(stderr, " * Type: proxy\n");
+ break;
+ default:
+ fprintf(stderr, " * Type: unknown\n");
+ break;
+ }
+ fprintf(stderr, " * Pretty name: %s\n", scfg->pretty_name ? scfg->pretty_name : "<undefined>");
+ fprintf(stderr, " * Announce port: %" PRIu16 "\n", scfg->announce_port);
+ fprintf(stderr, " * Listening ports:\n");
+ list_for_each_entry(local, &scfg->locals, list)
+ fprintf(stderr, " * %s\n", local->addrstr);
+ fprintf(stderr, " * Remote ports:\n");
+ list_for_each_entry(remote, &scfg->remotes, list)
+ fprintf(stderr, " * %s\n", remote->addrstr);
+}
+
+/*
+struct server_active_proxy {
+ struct sockaddr_in46 client;
+ char clientstr[ADDRSTRLEN];
+ struct sockaddr_in46 server;
+ char serverstr[ADDRSTRLEN]
+ struct uring_task task;
+ char buf[4096];
+ size_t len;
+ struct list_head list;
+};
+*/
+
+static void
+server_proxy_free(struct uring_task *task)
+{
+ struct server_proxy *proxy = container_of(task, struct server_proxy, task);
+
+ list_del(&proxy->list);
+ free(proxy);
+}
+
+static void
+server_proxy_connected(struct cfg *cfg, struct uring_task *task, int res)
+{
+ //struct server_proxy *proxy = container_of(task, struct server_proxy, task);
+
+ fprintf(stderr, "%s: connected %i\n", __func__, res);
+ return;
+}
+
+static void
+server_local_free(struct uring_task *task)
+{
+ struct server_local *local = container_of(task, struct server_local, task);
+
+ fprintf(stderr, "%s called: task 0x%p\n", __func__, task);
+
+ list_del(&local->list);
+ free(local);
+}
+
+static void
+server_local_accept(struct cfg *cfg, struct uring_task *task, int res)
+{
+ struct server_local *local = container_of(task, struct server_local, task);
+ struct server *scfg = container_of(task->parent, struct server, task);
+ struct server_proxy *proxy;
+ struct server_remote *remote;
+ int sfd;
+
+ fprintf(stderr, "%s called: task 0x%p and res %i\n", __func__, task, res);
+ fprintf(stderr, "%s called: scfg name is %s\n", __func__, scfg->name);
+
+ if (task->dead) {
+ fprintf(stderr, "Task dead!\n");
+ uring_task_put(cfg, task);
+ return;
+ }
+
+ if (res < 0) {
+ fprintf(stderr, "%s: result was %i\n", __func__, res);
+ goto out;
+ }
+
+ server_print_addr(&local->peer, local->peerstr, sizeof(local->peerstr));
+ fprintf(stderr, "%s: incoming proxy connection: %s -> %s\n", scfg->name, local->peerstr, local->addrstr);
+
+ if (list_empty(&scfg->remotes)) {
+ error("scfg->remotes empty!\n");
+ goto out;
+ }
+
+ proxy = zmalloc(sizeof(*proxy));
+ if (!proxy) {
+ perror("malloc");
+ uring_close(cfg, NULL, res, NULL);
+ goto out;
+ }
+
+ remote = list_first_entry(&scfg->remotes, struct server_remote, list);
+ fprintf(stderr, "%s: attempting proxy connection to %s (len %u)\n", scfg->name, remote->addrstr, remote->addr.addrlen);
+
+ sfd = socket(remote->addr.storage.ss_family, SOCK_STREAM, 0);
+ if (sfd < 0) {
+ perror("socket");
+ uring_close(cfg, NULL, res, NULL);
+ goto out;
+ }
+
+ proxy->client = local->peer;
+ memcpy(proxy->clientstr, local->peerstr, sizeof(proxy->clientstr));
+ uring_task_init(&proxy->task, "proxy", &scfg->task, server_proxy_free);
+ uring_task_set_fd(&proxy->task, sfd);
+ list_add(&proxy->list, &scfg->proxys);
+ uring_connect(cfg, &proxy->task, &remote->addr, server_proxy_connected);
+
+out:
+ uring_accept(cfg, &local->task, &local->peer, server_local_accept);
+}
+
+static bool
+server_local_open(struct cfg *cfg, struct server *scfg, struct server_local *local)
+{
+ int sfd;
+ int enable = 1;
+ int r;
+
+ sfd = socket(local->addr.storage.ss_family, SOCK_STREAM, 0);
+ if (sfd < 0) {
+ perror("socket");
+ goto out;
+ }
+
+ if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) {
+ perror("setsockopt");
+ goto out;
+ }
+
+ r = bind(sfd, (struct sockaddr *)&local->addr.storage, local->addr.addrlen);
+ if (r < 0) {
+ perror("bind");
+ goto out;
+ }
+
+ r = listen(sfd, 100);
+ if (r < 0) {
+ perror("listen");
+ goto out;
+ }
+
+ uring_task_set_fd(&local->task, sfd);
+ uring_accept(cfg, &local->task, &local->peer, server_local_accept);
+ fprintf(stderr, "** Opened listening socket: %s\n", local->addrstr);
+ return true;
+
+out:
+ if (sfd >= 0)
+ close(sfd);
+ return false;
+}
+
+bool
+server_commit(struct cfg *cfg, struct server *scfg)
+{
+ struct server_local *local;
+
+ /* FIXME: config, dont reread config if server running, make sure fd is available before this is called */
+ /* FIXME: verify correct cfg */
+ server_dump(scfg);
+
+ list_for_each_entry(local, &scfg->locals, list) {
+ server_local_open(cfg, scfg, local);
+ }
+ return true;
+}
+
+bool
+server_add_remote(struct cfg *cfg, struct server *scfg, struct sockaddr_in46 *addr)
+{
+ struct server_remote *remote;
+
+ if (!scfg || !addr)
+ return false;
+
+ remote = zmalloc(sizeof(*remote));
+ if (!remote)
+ return false;
+
+ remote->addr.storage = addr->storage;
+ remote->addr.addrlen = addr->addrlen;
+ server_print_addr(&remote->addr, remote->addrstr, sizeof(remote->addrstr));
+ list_add(&remote->list, &scfg->remotes);
+ return true;
+}
+
+bool
+server_add_local(struct cfg *cfg, struct server *scfg, struct sockaddr_in46 *addr)
+{
+ struct server_local *local;
+
+ if (!scfg || !addr)
+ return false;
+
+ local = zmalloc(sizeof(*local));
+ if (!local)
+ return false;
+
+ local->addr.storage = addr->storage;
+ local->addr.addrlen = addr->addrlen;
+ uring_task_init(&local->task, "local", &scfg->task, server_local_free);
+ server_print_addr(&local->addr, local->addrstr, sizeof(local->addrstr));
+ fprintf(stderr, "Adding local: %s\n", local->addrstr);
+ list_add(&local->list, &scfg->locals);
+ return true;
+}
+
+bool
+server_set_port(struct cfg *cfg, struct server *scfg, uint16_t port)
+{
+ if (!scfg || scfg->announce_port != 0)
+ return false;
+
+ scfg->announce_port = htons(port);
+ return true;
+}
+
+bool
+server_set_type(struct cfg *cfg, struct server *scfg, enum server_type type)
+{
+ if (!scfg || scfg->type != SERVER_TYPE_UNDEFINED)
+ return false;
+
+ switch (type) {
+ case SERVER_TYPE_ANNOUNCE:
+ scfg->type = SERVER_TYPE_ANNOUNCE;
+ break;
+ case SERVER_TYPE_PROXY:
+ scfg->type = SERVER_TYPE_PROXY;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+bool
+server_set_pretty_name(struct cfg *cfg, struct server *scfg, const char *pretty_name)
+{
+ if (!pretty_name || pretty_name[0] == '\0' || !scfg || scfg->pretty_name)
+ return false;
+
+ scfg->pretty_name = strdup(pretty_name);
+ if (!scfg->pretty_name)
+ return false;
+
+ return true;
+}
+
+struct server *
+server_new(struct cfg *cfg, const char *name)
+{
+ struct server *scfg;
+
+ list_for_each_entry(scfg, &cfg->servers, list) {
+ if (strcmp(name, scfg->name))
+ continue;
+ debug(2, "Server already exists: %s\n", name);
+ return scfg;
+ }
+
+ debug(2, "Would add server cfg: %s\n", name);
+ scfg = zmalloc(sizeof(*scfg));
+ if (!scfg) {
+ error("malloc");
+ return NULL;
+ }
+
+ scfg->type = SERVER_TYPE_UNDEFINED;
+ scfg->name = strdup(name);
+ scfg->running = false;
+ uring_task_init(&scfg->task, "scfg", &cfg->task, server_free);
+ list_init(&scfg->remotes);
+ list_init(&scfg->locals);
+ list_init(&scfg->proxys);
+ list_add(&scfg->list, &cfg->servers);
+
+ return scfg;
+}
diff --git a/server.h b/server.h
new file mode 100644
index 0000000..d066588
--- /dev/null
+++ b/server.h
@@ -0,0 +1,51 @@
+#ifndef fooserverhfoo
+#define fooserverhfoo
+
+enum server_type {
+ SERVER_TYPE_UNDEFINED,
+ SERVER_TYPE_ANNOUNCE,
+ SERVER_TYPE_PROXY
+};
+
+struct server {
+ enum server_type type;
+ char *name;
+ char *pretty_name;
+ uint16_t announce_port;
+ struct list_head locals;
+ struct list_head remotes;
+ struct list_head proxys;
+ bool running;
+
+ struct uring_task task;
+ char buf[4096];
+ size_t len;
+ struct list_head list;
+};
+
+void server_refdump(struct server *server);
+
+void server_delete(struct cfg *cfg, struct server *scfg);
+
+void server_delete_by_name(struct cfg *cfg, const char *name);
+
+bool server_commit(struct cfg *cfg, struct server *scfg);
+
+bool server_add_remote(struct cfg *cfg, struct server *scfg,
+ struct sockaddr_in46 *remote);
+
+bool server_add_local(struct cfg *cfg, struct server *scfg,
+ struct sockaddr_in46 *local);
+
+bool server_set_port(struct cfg *cfg, struct server *scfg, uint16_t port);
+
+bool server_set_type(struct cfg *cfg, struct server *scfg,
+ enum server_type type);
+
+bool server_set_pretty_name(struct cfg *cfg, struct server *scfg,
+ const char *pretty_name);
+
+struct server *server_new(struct cfg *cfg, const char *name);
+
+#endif
+
diff --git a/stest.c b/stest.c
new file mode 100644
index 0000000..d60ae74
--- /dev/null
+++ b/stest.c
@@ -0,0 +1,102 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <string.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdbool.h>
+
+#define PIPE_RD 0
+#define PIPE_WR 1
+
+int
+main(int argc, char **argv) {
+ int sfd;
+ struct sockaddr_in addr;
+ socklen_t addrsz = sizeof(addr);
+ int pfd[2];
+ int r;
+ int cfd;
+ int zfd;
+ size_t total = 0;
+
+ if (argc != 3) {
+ fprintf(stderr, "Usage: %s <addr> <port>\n", argv[0]);
+ exit(EXIT_FAILURE);
+ }
+
+ sfd = socket(AF_INET, SOCK_STREAM, 0);
+ if (sfd < 0) {
+ perror("socket");
+ exit(EXIT_FAILURE);
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(atoi(argv[2]));
+ addr.sin_addr.s_addr = inet_addr(argv[1]);
+
+ int enable = 1;
+ if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) {
+ perror("setsockopt");
+ exit(EXIT_FAILURE);
+ }
+
+ r = bind(sfd, (struct sockaddr *)&addr, sizeof(addr));
+ if (r < 0) {
+ perror("bind");
+ exit(EXIT_FAILURE);
+ }
+
+ r = listen(sfd, 100);
+ if (r < 0) {
+ perror("listen");
+ exit(EXIT_FAILURE);
+ }
+
+ cfd = accept4(sfd, (struct sockaddr *)&addr, &addrsz, SOCK_CLOEXEC);
+ if (cfd < 0) {
+ perror("accept");
+ exit(EXIT_FAILURE);
+ }
+
+ zfd = open("/dev/null", O_WRONLY | O_CLOEXEC);
+ if (zfd < 0) {
+ perror("open");
+ exit(EXIT_FAILURE);
+ }
+
+ if (pipe2(pfd, O_CLOEXEC) < 0) {
+ perror("pipe2");
+ exit(EXIT_FAILURE);
+ }
+
+ while (true) {
+ ssize_t r, w;
+
+ r = splice(cfd, NULL, pfd[PIPE_WR], NULL, 64 * 1024, SPLICE_F_MOVE);
+ if (r < 0)
+ perror("splice");
+ //fprintf(stderr, "Read %zi bytes from socket\n", r);
+ if (r == 0)
+ break;
+ w = splice(pfd[PIPE_RD], NULL, zfd, NULL, r, SPLICE_F_MOVE);
+ if (w < 0)
+ perror("splice");
+ if (w != r) {
+ fprintf(stderr, "Losing bytes\n");
+ exit(EXIT_FAILURE);
+ }
+ //fprintf(stderr, "Wrote %zi bytes to /dev/null\n", r);
+
+ total += w;
+ }
+
+ printf("Server: received %zu bytes\n", total);
+}
+
diff --git a/uring.c b/uring.c
new file mode 100644
index 0000000..c43a8fb
--- /dev/null
+++ b/uring.c
@@ -0,0 +1,294 @@
+#include <liburing.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+
+#include "main.h"
+#include "uring.h"
+#include "config.h"
+
+struct uring_ev {
+ struct io_uring uring;
+ struct io_uring_params uring_params;
+ struct uring_task task;
+};
+
+void
+uring_task_refdump(struct uring_task *task)
+{
+ char buf[4096];
+ struct uring_task *tmp;
+
+ buf[0] = '\0';
+ for (tmp = task; tmp; tmp = tmp->parent) {
+ size_t prefix;
+ char *dst;
+
+ if (tmp->parent)
+ prefix = strlen("->") + strlen(tmp->name);
+ else
+ prefix = strlen(tmp->name);
+
+ memmove(&buf[prefix], &buf[0], strlen(buf) + 1);
+
+ dst = &buf[0];
+ if (tmp->parent) {
+ *dst++ = '-';
+ *dst++ = '>';
+ }
+
+ memcpy(dst, tmp->name, strlen(tmp->name));
+ }
+
+ fprintf(stderr, "%s (0x%p parent 0x%p free 0x%p fd %i ref %u)\n",
+ buf, task, task->parent, task->free, task->fd,
+ task->refcount);
+}
+
+void
+uring_task_put(struct cfg *cfg, struct uring_task *task)
+{
+ struct uring_task *parent = task->parent;
+
+ fprintf(stderr, "%s: called with task 0x%p and refcount %u\n", __func__, task, task->refcount);
+ task->refcount--;
+
+ if (task->refcount > 0)
+ return;
+
+ if (task->refcount < 0)
+ error("Negative refcount!\n");
+
+ if (task->fd >= 0) {
+ uring_task_close_fd(cfg, task);
+ /* We'll be called again once the fd is closed */
+ return;
+ }
+
+ if (task->free)
+ task->free(task);
+
+ if (parent)
+ uring_task_put(NULL, parent);
+}
+
+void
+uring_task_get(struct cfg *cfg, struct uring_task *task)
+{
+ fprintf(stderr, "%s: called with task 0x%p and refcount %u\n", __func__, task, task->refcount);
+
+ if (task->refcount < 0)
+ error("Negative refcount!\n");
+
+ task->refcount++;
+}
+
+void
+uring_task_set_fd(struct uring_task *task, int fd)
+{
+ if (task->fd >= 0)
+ error("Leaking fd %i\n", task->fd);
+
+ task->fd = fd;
+}
+
+void
+uring_task_close_fd(struct cfg *cfg, struct uring_task *task)
+{
+ fprintf(stderr, "%s called with task 0x%p\n", __func__, task);
+ if (task->fd < 0)
+ return;
+
+ uring_close(cfg, task, task->fd, NULL);
+ task->fd = -1;
+}
+
+void
+uring_task_init(struct uring_task *task, const char *name, struct uring_task *parent, void (*free)(struct uring_task *))
+{
+ if (!free)
+ die("uring_task_init called without destructor\n");
+
+ task->refcount = 1;
+ task->fd = -1;
+ task->parent = parent;
+ task->free = free;
+ task->dead = false;
+ task->name = name;
+
+ if (task->parent)
+ uring_task_get(NULL, task->parent);
+}
+
+void
+uring_close(struct cfg *cfg, struct uring_task *task, int fd, callback_t callback)
+{
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&cfg->uev->uring);
+
+ fprintf(stderr, "%s: called with task 0x%p and cb 0x%p\n", __func__, task, callback);
+ if (!sqe)
+ perrordie("io_uring_sqe");
+
+ if (task) {
+ uring_task_get(cfg, task);
+ task->callback = callback;
+ }
+ io_uring_prep_close(sqe, fd);
+ io_uring_sqe_set_data(sqe, task);
+
+ fprintf(stderr, "%s: done\n", __func__);
+}
+
+void
+uring_read(struct cfg *cfg, struct uring_task *task, void *buf, size_t len, off_t offset, callback_t callback)
+{
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&cfg->uev->uring);
+
+ if (!sqe)
+ perrordie("io_uring_sqe");
+
+ if (task->fd < 0) {
+ error("uring_read called with no fd set\n");
+ return;
+ }
+
+ uring_task_get(cfg, task);
+ task->callback = callback;
+ io_uring_prep_read(sqe, task->fd, buf, len, offset);
+ io_uring_sqe_set_data(sqe, task);
+}
+
+void
+uring_openat(struct cfg *cfg, struct uring_task *task, const char *path, callback_t callback)
+{
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&cfg->uev->uring);
+
+ if (!sqe)
+ perrordie("io_uring_sqe");
+
+ uring_task_get(cfg, task);
+ task->callback = callback;
+ io_uring_prep_openat(sqe, AT_FDCWD, path, O_RDONLY | O_CLOEXEC, 0);
+ io_uring_sqe_set_data(sqe, task);
+}
+
+void
+uring_connect(struct cfg *cfg, struct uring_task *task, struct sockaddr_in46 *addr, callback_t callback)
+{
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&cfg->uev->uring);
+
+ if (!sqe)
+ perrordie("io_uring_sqe");
+
+ if (task->fd < 0) {
+ error("uring_connect called with no fd set\n");
+ return;
+ }
+
+ uring_task_get(cfg, task);
+ task->callback = callback;
+ io_uring_prep_connect(sqe, task->fd, (struct sockaddr *)&addr->storage, addr->addrlen);
+ io_uring_sqe_set_data(sqe, task);
+}
+
+void
+uring_accept(struct cfg *cfg, struct uring_task *task, struct sockaddr_in46 *addr, callback_t callback)
+{
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&cfg->uev->uring);
+ addr->addrlen = sizeof(addr->storage);
+
+ if (!sqe)
+ perrordie("io_uring_sqe");
+
+ if (task->fd < 0) {
+ error("uring_accept called with no fd set\n");
+ return;
+ }
+
+ uring_task_get(cfg, task);
+ task->callback = callback;
+ io_uring_prep_accept(sqe, task->fd, (struct sockaddr *)&addr->storage, &addr->addrlen, 0);
+ io_uring_sqe_set_data(sqe, task);
+}
+
+void
+uring_cancel(struct cfg *cfg, struct uring_task *task)
+{
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&cfg->uev->uring);
+
+ if (!sqe)
+ perrordie("io_uring_sqe");
+
+ task->dead = true;
+ io_uring_prep_cancel(sqe, task, 0);
+ io_uring_sqe_set_data(sqe, NULL);
+}
+
+static void
+uring_free(struct uring_task *task)
+{
+ fprintf(stderr, "%s called\n", __func__);
+}
+
+void
+uring_refdump(struct uring_ev *uev)
+{
+ uring_task_refdump(&uev->task);
+}
+
+void
+uring_init(struct cfg *cfg)
+{
+ struct uring_ev *uev;
+
+ uev = zmalloc(sizeof(*uev));
+ if (!uev)
+ perrordie("malloc");
+
+ if (io_uring_queue_init_params(4096, &uev->uring, &uev->uring_params) < 0)
+ perrordie("io_uring_queue_init_params");
+
+ fprintf(stderr, "uring initialized, features: 0x%08x\n", uev->uring_params.features);
+
+ uring_task_init(&uev->task, "uev", &cfg->task, uring_free);
+ cfg->uev = uev;
+}
+
+int
+uring_event_loop(struct cfg *cfg)
+{
+ while (true) {
+ struct io_uring_cqe *cqe;
+ unsigned nr, head;
+ int r;
+
+ io_uring_submit(&cfg->uev->uring);
+ r = io_uring_wait_cqe(&cfg->uev->uring, &cqe);
+ if (r < 0) {
+ if (errno == EINTR)
+ continue;
+ else
+ perrordie("io_uring_wait_cqe");
+ }
+
+ nr = 0;
+ io_uring_for_each_cqe(&cfg->uev->uring, head, cqe) {
+ struct uring_task *task = io_uring_cqe_get_data(cqe);
+
+ fprintf(stderr, "%s: got CEQ (res: %i, task: 0x%p, cb: 0x%p)\n", __func__, cqe->res, task, task ? task->callback : NULL);
+ if (task && task->callback)
+ task->callback(cfg, task, cqe->res);
+ nr++;
+ if (task)
+ uring_task_put(cfg, task);
+ }
+
+ printf("%s: %u CQEs treated\n", __func__, nr);
+ io_uring_cq_advance(&cfg->uev->uring, nr);
+ }
+
+ return 0;
+}
+
diff --git a/uring.h b/uring.h
new file mode 100644
index 0000000..4c5ee17
--- /dev/null
+++ b/uring.h
@@ -0,0 +1,41 @@
+#ifndef foouringhfoo
+#define foouringhfoo
+
+void uring_task_refdump(struct uring_task *task);
+
+void uring_task_put(struct cfg *cfg, struct uring_task *task);
+
+void uring_task_get(struct cfg *cfg, struct uring_task *task);
+
+void uring_task_set_fd(struct uring_task *task, int fd);
+
+void uring_task_close_fd(struct cfg *cfg, struct uring_task *task);
+
+void uring_task_init(struct uring_task *task, const char *name,
+ struct uring_task *parent,
+ void (*free)(struct uring_task *));
+
+void uring_close(struct cfg *cfg, struct uring_task *task, int fd,
+ callback_t callback);
+
+void uring_read(struct cfg *cfg, struct uring_task *task, void *buf,
+ size_t len, off_t offset, callback_t callback);
+
+void uring_openat(struct cfg *cfg, struct uring_task *task, const char *path,
+ callback_t callback);
+
+void uring_connect(struct cfg *cfg, struct uring_task *task,
+ struct sockaddr_in46 *addr, callback_t callback);
+
+void uring_accept(struct cfg *cfg, struct uring_task *task,
+ struct sockaddr_in46 *addr, callback_t callback);
+
+void uring_cancel(struct cfg *cfg, struct uring_task *task);
+
+void uring_refdump(struct uring_ev *uev);
+
+void uring_init(struct cfg *cfg);
+
+int uring_event_loop(struct cfg *cfg);
+
+#endif
diff --git a/utils.c b/utils.c
new file mode 100644
index 0000000..a84f63b
--- /dev/null
+++ b/utils.c
@@ -0,0 +1,37 @@
+#include <stdlib.h>
+#include <errno.h>
+#include <stdint.h>
+#include <limits.h>
+
+int
+strtou16_strict(const char *str, uint16_t *result)
+{
+ char *end;
+ long val;
+
+ if (!str)
+ return -EINVAL;
+
+ errno = 0;
+ val = strtol(str, &end, 10);
+
+ if (errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
+ return -EINVAL;
+
+ if (errno != 0 && val == 0)
+ return -EINVAL;
+
+ if (end == str)
+ return -EINVAL;
+
+ if (*end != '\0')
+ return -EINVAL;
+
+ if (val < 1 || val > UINT16_MAX)
+ return -EINVAL;
+
+ if (result)
+ *result = val;
+ return 0;
+}
+
diff --git a/utils.h b/utils.h
new file mode 100644
index 0000000..ccebb30
--- /dev/null
+++ b/utils.h
@@ -0,0 +1,138 @@
+#ifndef fooutilshfoo
+#define fooutilshfoo
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+int strtou16_strict(const char *str, uint16_t *result);
+
+struct list_head {
+ struct list_head *next;
+ struct list_head *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name)
+
+static inline void list_init(struct list_head *list)
+{
+ list->next = list;
+ list->prev = list;
+}
+
+static inline void list_del(struct list_head *list)
+{
+ list->next->prev = list->prev;
+ list->prev->next = list->next;
+}
+
+static inline void list_add(struct list_head *new, struct list_head *list)
+{
+ list->next->prev = new;
+ new->next = list->next;
+ new->prev = list;
+ list->next = new;
+}
+
+static inline void list_replace(struct list_head *old, struct list_head *new)
+{
+ old->prev->next = new;
+ old->next->prev = new;
+ new->next = old->next;
+ new->prev = old->prev;
+ old->next = old->prev = NULL;
+}
+
+static inline bool list_empty(struct list_head *list)
+{
+ return list->next == list;
+}
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
+
+#define container_of(ptr, type, member) ({ \
+ const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) );})
+
+#define list_entry(ptr, type, member) \
+ container_of(ptr, type, member)
+
+#define list_first_entry(ptr, type, member) \
+ list_entry((ptr)->next, type, member)
+
+#define list_next_entry(pos, member) \
+ list_entry((pos)->member.next, typeof(*(pos)), member)
+
+#define list_for_each(pos, head) \
+ for (pos = (head)->next; pos != (head); pos = pos->next)
+
+#define list_for_each_entry(pos, head, member) \
+ for (pos = list_first_entry(head, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = list_next_entry(pos, member))
+
+#define list_for_each_entry_safe(pos, n, head, member) \
+ for (pos = list_entry((head)->next, typeof(*pos), member), \
+ n = list_entry(pos->member.next, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+static inline void* zmalloc(size_t size)
+{
+ return calloc(1, size);
+}
+
+/*
+#define _cleanup_(x) __attribute__((cleanup(x)))
+
+#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \
+ static inline void func##p(type *p) { \
+ if (*p) \
+ func(*p); \
+ } \
+ struct __useless_struct_to_allow_trailing_semicolon__
+
+static inline unsigned
+strv_length(char **strv)
+{
+ unsigned len;
+
+ for (len = 0; strv && *strv; strv++)
+ len++;
+
+ return len;
+}
+
+static inline void strv_free(char **l) {
+ char **k;
+ if (l) {
+ for (k = l; *k; k++)
+ free(k);
+ free(l);
+ }
+}
+DEFINE_TRIVIAL_CLEANUP_FUNC(char **, strv_free);
+#define _cleanup_strv_free_ _cleanup_(strv_freep)
+
+static inline void freep(void *p) {
+ free(*(void**) p);
+}
+#define _cleanup_free_ _cleanup_(freep)
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(int, close);
+#define _cleanup_close_ _cleanup_(closep)
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(FILE *, fclose);
+#define _cleanup_fclose_ _cleanup_(fclosep)
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(DIR *, closedir);
+#define _cleanup_closedir_ _cleanup_(closedirp)
+
+*/
+
+#endif
+