summaryrefslogtreecommitdiff
path: root/inotify.c
diff options
context:
space:
mode:
Diffstat (limited to 'inotify.c')
-rw-r--r--inotify.c730
1 files changed, 730 insertions, 0 deletions
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);
+}
+