/* * Copyright (c) 2023 Egor Tensin * This file is part of the "cimple" project. * For details, see https://github.com/egor-tensin/cimple. * Distributed under the MIT License. */ #include "event_loop.h" #include "log.h" #include "net.h" #include "string.h" #include #include #include #include SIMPLEQ_HEAD(event_fd_queue, event_fd); struct event_fd { int fd; short events; event_handler handler; void *arg; SIMPLEQ_ENTRY(event_fd) entries; }; static struct event_fd *event_fd_create(int fd, short events, event_handler handler, void *arg) { struct event_fd *res = calloc(1, sizeof(struct event_fd)); if (!res) { log_errno("malloc"); return NULL; } res->fd = fd; res->events = events; res->handler = handler; res->arg = arg; return res; } static void event_fd_destroy(struct event_fd *entry) { free(entry); } static void event_fd_queue_create(struct event_fd_queue *queue) { SIMPLEQ_INIT(queue); } static void event_fd_queue_destroy(struct event_fd_queue *queue) { struct event_fd *entry1 = SIMPLEQ_FIRST(queue); while (entry1) { struct event_fd *entry2 = SIMPLEQ_NEXT(entry1, entries); event_fd_destroy(entry1); entry1 = entry2; } SIMPLEQ_INIT(queue); } struct event_loop { nfds_t nfds; struct event_fd_queue entries; }; int event_loop_create(struct event_loop **_loop) { int ret = 0; struct event_loop *loop = calloc(1, sizeof(struct event_loop)); if (!loop) { log_errno("calloc"); return -1; } *_loop = loop; event_fd_queue_create(&loop->entries); return ret; } void event_loop_destroy(struct event_loop *loop) { event_fd_queue_destroy(&loop->entries); free(loop); } int event_loop_add(struct event_loop *loop, int fd, short events, event_handler handler, void *arg) { log("Adding descriptor %d to event loop\n", fd); struct event_fd *entry = event_fd_create(fd, events, handler, arg); if (!entry) return -1; nfds_t nfds = loop->nfds + 1; SIMPLEQ_INSERT_TAIL(&loop->entries, entry, entries); loop->nfds = nfds; return 0; } static void event_loop_remove(struct event_loop *loop, struct event_fd *entry) { log("Removing descriptor %d from event loop\n", entry->fd); SIMPLEQ_REMOVE(&loop->entries, entry, event_fd, entries); net_close(entry->fd); event_fd_destroy(entry); --loop->nfds; } static char *append_event(char *buf, size_t sz, char *ptr, const char *event) { if (ptr > buf) ptr = stpecpy(ptr, buf + sz, ","); return stpecpy(ptr, buf + sz, event); } static char *events_to_string(short events) { const size_t sz = 128; char *buf = calloc(1, sz); if (!buf) return NULL; char *ptr = buf; if (events & POLLNVAL) ptr = append_event(buf, sz, ptr, "POLLNVAL"); if (events & POLLERR) ptr = append_event(buf, sz, ptr, "POLLERR"); if (events & POLLHUP) ptr = append_event(buf, sz, ptr, "POLLHUP"); if (events & POLLIN) ptr = append_event(buf, sz, ptr, "POLLIN"); if (events & POLLOUT) ptr = append_event(buf, sz, ptr, "POLLOUT"); return buf; } static struct pollfd *make_pollfds(const struct event_loop *loop) { struct pollfd *fds = calloc(loop->nfds, sizeof(struct pollfd)); if (!fds) { log_errno("calloc"); return NULL; } struct event_fd *entry = SIMPLEQ_FIRST(&loop->entries); for (nfds_t i = 0; i < loop->nfds; ++i, entry = SIMPLEQ_NEXT(entry, entries)) { fds[i].fd = entry->fd; fds[i].events = entry->events; } log("Descriptors:\n"); for (nfds_t i = 0; i < loop->nfds; ++i) { char *events = events_to_string(fds[i].events); log(" %d (%s)\n", fds[i].fd, events ? events : ""); free(events); } return fds; } int event_loop_run(struct event_loop *loop) { struct pollfd *fds = make_pollfds(loop); if (!fds) return -1; int ret = poll(fds, loop->nfds, -1); if (ret < 0) { log_errno("poll"); return ret; } ret = 0; struct event_fd *entry = SIMPLEQ_FIRST(&loop->entries); for (nfds_t i = 0; i < loop->nfds; ++i) { struct event_fd *next = SIMPLEQ_NEXT(entry, entries); if (!fds[i].revents) goto next; char *events = events_to_string(fds[i].revents); log("Descriptor %d is ready: %s\n", fds[i].fd, events ? events : ""); free(events); /* Execute all handlers but notice if any of them fail. */ const int handler_ret = entry->handler(loop, fds[i].fd, fds[i].revents, entry->arg); switch (handler_ret) { case 0: goto next; case EVENT_LOOP_REMOVE: goto remove; default: break; } remove: event_loop_remove(loop, entry); goto next; next: entry = next; continue; } free(fds); return ret; }