commit bb3f9a0bc6448654b44f1955933ca89bc80d2736 Author: pantonshire Date: Mon May 25 06:49:18 2026 +0100 initial commit diff --git a/examples/eventloop.c b/examples/eventloop.c new file mode 100644 index 0000000..8e5731c --- /dev/null +++ b/examples/eventloop.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include + +int main() { + struct tlsl_event timer, pipe_ev; + EVENTLOOP_STACK_SCOPED(el); + tlsl_event_id_t id; + uint32_t flags; + + int pfds[2]; + if (pipe(pfds)) + return 1; + + pid_t pid = fork(); + if (pid < 0) + return 1; + + if (!pid) { + char buf[] = "hello!"; + sleep(5); + write(pfds[1], buf, sizeof(buf)); + exit(0); + } + + if (tlsl_eventloop_init(&el)) + return 1; + + if (tlsl_eventloop_add_timer(&el, &timer, 2000, false)) + return 1; + + if (tlsl_eventloop_add_fd(&el, &pipe_ev, pfds[0], TLSL_EVENT_READABLE)) + return 1; + + for (;;) { + if (tlsl_eventloop_wait(&el, &id, &flags, -1)) { + fprintf(stderr, "failed to wait"); + continue; + } + + if (tlsl_event_id_eq(id, timer.id)) { + tlsl_eventloop_clear(&el, &timer); + printf("timer tick flags=%u\n", flags); + } + else if (tlsl_event_id_eq(id, pipe_ev.id)) { + char buf[1024] = { 0 }; + read(pfds[0], buf, sizeof(buf) - 1); + printf("pipe event flags=%u buf=%s\n", flags, buf); + break; + } + else { + fprintf(stderr, "unknown id"); + } + } + + tlsl_eventloop_remove(&el, &timer); + tlsl_eventloop_remove(&el, &pipe_ev); + + return 0; +} diff --git a/include/eventloop_platform.h b/include/eventloop_platform.h new file mode 100644 index 0000000..828ae1d --- /dev/null +++ b/include/eventloop_platform.h @@ -0,0 +1,42 @@ +#pragma once + +#include "public/typecheck.h" +#include "public/eventloop.h" + +#define TLSL_PLATFORM_EVENT_DATA(EVENT) ({ \ + TLSL_CHECK_TYPE(struct tlsl_event *, EVENT); \ + TLSL_CHECK_ARRAY((EVENT)->_platform); \ + _Static_assert(sizeof((EVENT)->_platform) >= sizeof(struct tlsl_event_platform), \ + "event platform buf too small"); \ + _Static_assert(__alignof__((EVENT)->_platform) >= __alignof__(struct tlsl_event_platform), \ + "event platform buf alignment too small"); \ + (struct tlsl_event_platform *)(EVENT->_platform); \ +}) + +#define TLSL_DEFINE_PLATFORM_EVENT_DATA(EVENT, NAME) \ + struct tlsl_event_platform *NAME = TLSL_PLATFORM_EVENT_DATA(EVENT) + +#define TLSL_PLATFORM_EVENTLOOP_DATA(EL) ({ \ + TLSL_CHECK_TYPE(struct tlsl_eventloop *, EL); \ + TLSL_CHECK_ARRAY((EL)->_platform); \ + _Static_assert(sizeof((EL)->_platform) >= sizeof(struct tlsl_eventloop_platform), \ + "eventloop platform buf too small"); \ + _Static_assert(__alignof__((EL)->_platform) >= __alignof__(struct tlsl_eventloop_platform), \ + "eventloop platform buf alignment too small"); \ + (struct tlsl_eventloop_platform *)(EL->_platform); \ +}) + +#define TLSL_DEFINE_PLATFORM_EVENTLOOP_DATA(EL, NAME) \ + struct tlsl_eventloop_platform *NAME = TLSL_PLATFORM_EVENTLOOP_DATA(EL) + +struct tlsl_event_platform; +struct tlsl_eventloop_platform; + +int tlsl_platform_el_init(struct tlsl_eventloop *el); +void tlsl_platform_el_cleanup(struct tlsl_eventloop *el); +void tlsl_platform_el_event_remove(struct tlsl_eventloop *el, struct tlsl_event *e); +void tlsl_platform_el_event_cleanup(struct tlsl_eventloop *el, struct tlsl_event *e); +int tlsl_platform_el_event_clear(struct tlsl_eventloop *el, struct tlsl_event *e); +int tlsl_platform_el_wait(struct tlsl_eventloop *el, tlsl_event_id_t *id_out, uint32_t *flags_out, int timeout_ms); +int tlsl_platform_el_add_timer(struct tlsl_eventloop *el, struct tlsl_event *e, uint64_t millis, bool oneshot); +int tlsl_platform_el_add_fd(struct tlsl_eventloop *el, struct tlsl_event *e, int fd, uint32_t flags); diff --git a/include/public/array_list.h b/include/public/array_list.h new file mode 100644 index 0000000..9d675a6 --- /dev/null +++ b/include/public/array_list.h @@ -0,0 +1,54 @@ +#pragma once + +#include + +#include "scoped.h" + +#define ARRAY_LIST_EMPTY ((struct array_list) { NULL, 0, 0 }) + +struct array_list { + void *buf; + size_t len; + size_t cap; +}; + +int array_list_raw_alloc(struct array_list *al, size_t cap, size_t elem_size); +void array_list_raw_cleanup(struct array_list *al); + +#define DEF_ARRAY_LIST_TYPE(NAME, TYPE, UNOCCUPIED, CLEANUP_FN) \ + static inline TYPE *array_list_##NAME##_get(struct array_list *al, size_t i) { \ + if (i >= al->len) \ + return NULL; \ + return &((TYPE *)al->buf)[i]; \ + } \ + static inline const TYPE *array_list_##NAME##_cget(const struct array_list *al, size_t i) { \ + if (i >= al->len) \ + return NULL; \ + return &((const TYPE *)al->buf)[i]; \ + } \ + static inline void array_list_##NAME##_cleanup(struct array_list *al) { \ + size_t i; \ + for (i = 0; i < al->len; i++) \ + CLEANUP_FN(array_list_##NAME##_get(al, i)); \ + array_list_raw_cleanup(al); \ + } \ + static inline int array_list_##NAME##_alloc(struct array_list *al, size_t cap) { \ + size_t i; \ + if (cap < al->len) { \ + for (i = cap; i < al->len; i++) \ + CLEANUP_FN(array_list_##NAME##_get(al, i)); \ + al->len = cap; \ + } \ + return array_list_raw_alloc(al, cap, sizeof(TYPE)); \ + } \ + static inline int array_list_##NAME##_push_noalloc_byval(struct array_list *al, TYPE elem) { \ + if (al->len >= al->cap) \ + return -1; \ + ((TYPE *)al->buf)[al->len++] = elem; \ + return 0; \ + } \ + DEF_SCOPED_TYPE( \ + array_list_##NAME, \ + struct array_list, \ + ARRAY_LIST_EMPTY, \ + array_list_##NAME##_cleanup) diff --git a/include/public/container_of.h b/include/public/container_of.h new file mode 100644 index 0000000..dc5609b --- /dev/null +++ b/include/public/container_of.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "typecheck.h" + +#define TLSL_CONTAINER_OF(PTR, TYPE, MEMBER) ({ \ + TLSL_TYPEOFS_EQ(((TYPE *)NULL)->MEMBER, *(PTR)); \ + (TYPE *)((char *)(PTR) - offsetof(TYPE, MEMBER)); \ +}) diff --git a/include/public/eventloop.h b/include/public/eventloop.h new file mode 100644 index 0000000..82ac35e --- /dev/null +++ b/include/public/eventloop.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include + +#include "list.h" +#include "scoped.h" + +#define TLSL_EVENT_READABLE (1 << 0) +#define TLSL_EVENT_WRITABLE (1 << 1) + +enum tlsl_event_source { + TLSL_EVENT_SOURCE_FD, + TLSL_EVENT_SOURCE_TIMER, +}; + +typedef struct { + uintptr_t obj; + enum tlsl_event_source source; +} tlsl_event_id_t; + +struct tlsl_event { + tlsl_event_id_t id; + struct intrusive_list list; + char _platform[16] __attribute__((aligned)); +}; + +struct tlsl_eventloop { + struct intrusive_list event_list; + bool valid; + char _platform[16] __attribute__((aligned)); +}; + +static inline bool tlsl_event_id_eq(tlsl_event_id_t eid1, tlsl_event_id_t eid2) { + return (eid1.obj == eid2.obj) && (eid1.source == eid2.source); +} + +int tlsl_eventloop_init(struct tlsl_eventloop *el); +void tlsl_eventloop_cleanup(struct tlsl_eventloop *el); + +int tlsl_eventloop_wait(struct tlsl_eventloop *el, tlsl_event_id_t *id_out, uint32_t *flags_out, int timeout_ms); +int tlsl_eventloop_clear(struct tlsl_eventloop *el, struct tlsl_event *e); +void tlsl_eventloop_remove(struct tlsl_eventloop *el, struct tlsl_event *e); +int tlsl_eventloop_add_timer(struct tlsl_eventloop *el, struct tlsl_event *e, uint64_t millis, bool oneshot); +int tlsl_eventloop_add_fd(struct tlsl_eventloop *el, struct tlsl_event *e, int fd, uint32_t flags); + +DEF_SCOPED_TYPE(eventloop_stack, struct tlsl_eventloop, (struct tlsl_eventloop) { .valid = false }, tlsl_eventloop_cleanup) + +#define EVENTLOOP_STACK_SCOPED(VAL) SCOPED(eventloop_stack, struct tlsl_eventloop, VAL) +#define EVENTLOOP_STACK_CLOSE(EL) _scoped_eventloop_stack_cleanup(EL) diff --git a/include/public/io.h b/include/public/io.h new file mode 100644 index 0000000..882ba0e --- /dev/null +++ b/include/public/io.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +#include "scoped.h" + +static inline void _tlsl_close_if_nonnegative(int *fd) { + if (*fd >= 0) + close(*fd); +} + +DEF_SCOPED_TYPE(fd, int, -1, _tlsl_close_if_nonnegative) + +#define FD_SCOPED(VAL) SCOPED(fd, int, VAL) +#define FD_CLOSE(FD) _tlsl_scoped_fd_cleanup(FD) +#define FD_RELEASE(FD) _tlsl_scoped_fd_release(FD) + +static inline int setsockopt_int(int fd, int level, int option, int val) { + return setsockopt(fd, level, option, &val, sizeof(val)); +} diff --git a/include/public/list.h b/include/public/list.h new file mode 100644 index 0000000..a17d814 --- /dev/null +++ b/include/public/list.h @@ -0,0 +1,46 @@ +#pragma once + +#include "typecheck.h" + +struct intrusive_list { + struct intrusive_list *next; + struct intrusive_list *prev; +}; + +static inline void intrusive_list_init_empty(struct intrusive_list *head) { + head->next = head; + head->prev = head; +} + +static inline void intrusive_list_del(struct intrusive_list *node) { + struct intrusive_list old_node; + + old_node = *node; + node->prev->next = old_node.next; + node->next->prev = old_node.prev; +} + +static inline void intrusive_list_add( + struct intrusive_list *add_after, + struct intrusive_list *new_node) +{ + new_node->prev = add_after; + new_node->next = add_after->next; + new_node->prev->next = new_node; + new_node->next->prev = new_node; +} + +#define INTRUSIVE_LIST_FOR_EACH(ITER, NEXT, HEAD) \ + for ( \ + (void)TLSL_CHECK_TYPE(struct intrusive_list *, HEAD), \ + (ITER) = (HEAD)->next, \ + (NEXT) = (ITER)->next; \ + (ITER) != (HEAD); \ + (ITER) = (NEXT), (NEXT) = (ITER)->next) + +#define INTRUSIVE_LIST_FOR_EACH_NO_REMOVE(ITER, HEAD) \ + for ( \ + (void)TLSL_CHECK_TYPE(struct intrusive_list *, HEAD), \ + (ITER) = (HEAD)->next; \ + (ITER) != (HEAD); \ + (ITER) = (ITER)->next) diff --git a/include/public/mem.h b/include/public/mem.h new file mode 100644 index 0000000..f52325f --- /dev/null +++ b/include/public/mem.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include "scoped.h" +#include "typecheck.h" + +static inline void _tlsl_mem_free(void **p) { + free(*p); +} + +DEF_SCOPED_TYPE(mem, void *, NULL, _tlsl_mem_free) + +#define MEM_SCOPED(TYPE, VAL) SCOPED(mem, TYPE *, VAL) +#define MEM_FREE(TYPE, MEM) ({ TLSL_CHECK_TYPE(TYPE **, MEM), (TYPE *)_scoped_mem_cleanup((void **)(MEM)); }) +#define MEM_RELEASE(TYPE, MEM) ({ TLSL_CHECK_TYPE(TYPE **, MEM), (TYPE *)_scoped_mem_release((void **)(MEM)); }) diff --git a/include/public/scoped.h b/include/public/scoped.h new file mode 100644 index 0000000..88b07d6 --- /dev/null +++ b/include/public/scoped.h @@ -0,0 +1,22 @@ +#pragma once + +#define DEF_SCOPED_TYPE(NAME, TYPE, UNOCCUPIED, CLEANUP_FN) \ + static inline TYPE _tlsl_scoped_##NAME##_unoccupied(void) { \ + return (UNOCCUPIED); \ + } \ + static inline void _tlsl_scoped_##NAME##_cleanup(TYPE *_val) { \ + CLEANUP_FN(_val); \ + *_val = (UNOCCUPIED); \ + } \ + static inline void _tlsl_scoped_##NAME##_cleanup_raw(void *_val) { \ + _tlsl_scoped_##NAME##_cleanup((TYPE *)_val); \ + } \ + static inline TYPE _tlsl_scoped_##NAME##_release(TYPE *_val) { \ + TYPE _released = *_val; \ + *_val = (UNOCCUPIED); \ + return _released; \ + } + +#define SCOPED(NAME, TYPE, VAL) \ + TYPE VAL __attribute__((cleanup(_tlsl_scoped_##NAME##_cleanup_raw))) \ + = _tlsl_scoped_##NAME##_unoccupied() diff --git a/include/public/str.h b/include/public/str.h new file mode 100644 index 0000000..a92ec8c --- /dev/null +++ b/include/public/str.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include + +#define SVIEW_LIT(S) ((void)(S "LITERAL_CHECK"), (str_view_t) { S, sizeof(S) - 1 }) +#define SVIEW_NULL ((str_view_t) { NULL, 0 }) +#define SBUF_NULL ((str_buf_t) { NULL, 0 }) +#define SBUF_SCOPED(NAME) str_buf_t NAME __attribute__((cleanup(sb_free))) = SB_NULL + +#define SVIEW_PRI "%.*s" +#define SVIEW_PRI_ARGS(S) (int)((S).len), (S).ptr + +// String which is: +// - Not owned +// - Not necessarily heap-allocated +// - Not necessarily nul-terminated +typedef struct { + const char *ptr; + size_t len; +} str_view_t; + +// String which is: +// - Owned +// - Heap-allocated +// - Nul-terminated +// The length does not include the nul terminator. +typedef struct { + char *ptr; + size_t len; +} str_buf_t; + +static inline bool sview_is_null(str_view_t s) { + return !s.ptr; +} + +static inline bool sview_is_empty(str_view_t s) { + return !s.len; +} + +static inline bool sbuf_is_null(str_buf_t buf) { + return !buf.ptr; +} + +static inline bool sbuf_is_empty(str_buf_t buf) { + return !buf.len; +} + +static inline str_view_t sview_from_parts(const char *ptr, size_t len) { + return (str_view_t) { ptr, len }; +} + +static inline str_view_t sview_from_cstr(const char *ptr, size_t maxlen) { + size_t len; + + len = strnlen(ptr, maxlen); + return sview_from_parts(ptr, len); +} + +static inline str_view_t sview_from_cstr_unbounded(const char *ptr) { + size_t len = strlen(ptr); + return sview_from_parts(ptr, len); +} + +static inline bool sview_eq(str_view_t s1, str_view_t s2) { + return (s1.len == s2.len) && ((!s1.ptr || !s2.ptr) + ? (s1.ptr == s2.ptr) + : !memcmp(s1.ptr, s2.ptr, s1.len)); +} + +static inline str_view_t sview_substr(str_view_t s, size_t start, size_t end) { + if ((start > end) || (start > s.len) || (end > s.len)) + return SVIEW_NULL; + return sview_from_parts(s.ptr + start, end - start); +} + +static inline str_view_t sbuf_view(str_buf_t buf) { + return sview_from_parts(buf.ptr, buf.len); +} + +str_buf_t sbuf_from_sview(str_view_t s); + +static inline void sbuf_free(str_buf_t *buf) { + free(buf->ptr); + buf->ptr = NULL; + buf->len = 0; +} diff --git a/include/public/typecheck.h b/include/public/typecheck.h new file mode 100644 index 0000000..c78d76e --- /dev/null +++ b/include/public/typecheck.h @@ -0,0 +1,17 @@ +#pragma once + +#define TLSL_TYPES_EQ(TYPE_LHS, TYPE_RHS) ({ \ + _Static_assert(__builtin_types_compatible_p(TYPE_LHS, TYPE_RHS), \ + "types mismatch"); \ + 1; \ +}) + +#define TLSL_TYPEOFS_EQ(VAL_LHS, VAL_RHS) TLSL_TYPES_EQ(typeof(VAL_LHS), typeof(VAL_RHS)) + +#define TLSL_CHECK_TYPE(TYPE, VAL) TLSL_TYPES_EQ(TYPE, typeof(VAL)) + +#define TLSL_CHECK_ARRAY(VAL) ({ \ + _Static_assert(!__builtin_types_compatible_p(typeof(VAL), typeof(&(VAL)[0])), \ + "not an array"); \ + 1; \ +}) diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..9582585 --- /dev/null +++ b/meson.build @@ -0,0 +1,74 @@ +project( + 'tlsl', + 'c', + default_options : ['c_std=gnu11,c11'], + meson_version: '>=1.1' +) + +eventloop_epoll = 'epoll' +eventloop_kqueue = 'kqueue' + +platform_info = { + 'linux': { + 'eventloop': eventloop_epoll, + }, + 'freebsd': { + 'eventloop': eventloop_kqueue, + }, + 'openbsd': { + 'eventloop': eventloop_kqueue, + }, + 'netbsd': { + 'eventloop': eventloop_kqueue, + }, + 'darwin': { + 'eventloop': eventloop_kqueue, + }, +}[target_machine.system()] + +eventloop_info = { + eventloop_epoll: { + 'path': 'src/eventloop_epoll.c', + }, + eventloop_kqueue: { + 'path': 'src/eventloop_kqueue.c', + }, +}[platform_info['eventloop']] + +sources = [ + 'src/str.c', + 'src/eventloop.c', + 'src/array_list.c', + eventloop_info['path'], +] + +includes = include_directories('include') +public_includes = include_directories('include/public') + +args = [ + '-Wall', + '-DTLSL_PLATFORM_' + target_machine.system().to_upper(), + '-DTLSL_EVENTLOOP_' + platform_info['eventloop'].to_upper(), +] + +tlsl_lib = shared_library( + meson.project_name(), + sources, + include_directories: includes, + c_args: args, +) + +tlsl_dep = declare_dependency( + include_directories: public_includes, + link_with: tlsl_lib, +) + +if get_option('examples') + executable( + 'example_eventloop', + [ 'examples/eventloop.c' ], + dependencies: tlsl_dep, + install: false, + ) +endif + diff --git a/meson.options b/meson.options new file mode 100644 index 0000000..6ba82fa --- /dev/null +++ b/meson.options @@ -0,0 +1 @@ +option('examples', type: 'boolean', value: true, description: 'Build example binaries') diff --git a/src/array_list.c b/src/array_list.c new file mode 100644 index 0000000..5aab668 --- /dev/null +++ b/src/array_list.c @@ -0,0 +1,22 @@ +#include + +#include "public/array_list.h" + +int array_list_raw_alloc(struct array_list *al, size_t cap, size_t elem_size) { + void *buf; + + if (!(buf = realloc(al->buf, cap * elem_size))) + return -1; + al->buf = buf; + al->cap = cap; + return 0; +} + +void array_list_raw_cleanup(struct array_list *al) { + if (al->buf && al->cap) { + free(al->buf); + al->buf = NULL; + al->cap = 0; + al->len = 0; + } +} diff --git a/src/eventloop.c b/src/eventloop.c new file mode 100644 index 0000000..4a2ab51 --- /dev/null +++ b/src/eventloop.c @@ -0,0 +1,77 @@ +#include "eventloop_platform.h" +#include "public/container_of.h" +#include "public/list.h" + +int tlsl_eventloop_init(struct tlsl_eventloop *el) { + int platform_res; + + intrusive_list_init_empty(&el->event_list); + + if ((platform_res = tlsl_platform_el_init(el))) + return platform_res; + + el->valid = true; + return 0; +} + +void tlsl_eventloop_cleanup(struct tlsl_eventloop *el) { + struct intrusive_list *li; + struct tlsl_event *e; + + if (!el || !el->valid) + return; + + INTRUSIVE_LIST_FOR_EACH_NO_REMOVE(li, &el->event_list) { + e = TLSL_CONTAINER_OF(li, struct tlsl_event, list); + tlsl_platform_el_event_cleanup(el, e); + } + + tlsl_platform_el_cleanup(el); + el->valid = false; +} + +int tlsl_eventloop_wait( + struct tlsl_eventloop *el, + tlsl_event_id_t *id_out, + uint32_t *flags_out, + int timeout_ms) +{ + return tlsl_platform_el_wait(el, id_out, flags_out, timeout_ms); +} + +int tlsl_eventloop_clear(struct tlsl_eventloop *el, struct tlsl_event *e) { + return tlsl_platform_el_event_clear(el, e); +} + +void tlsl_eventloop_remove(struct tlsl_eventloop *el, struct tlsl_event *e) { + tlsl_platform_el_event_remove(el, e); + tlsl_platform_el_event_cleanup(el, e); +} + +int tlsl_eventloop_add_timer( + struct tlsl_eventloop *el, + struct tlsl_event *e, + uint64_t millis, + bool oneshot) +{ + int res; + + if ((res = tlsl_platform_el_add_timer(el, e, millis, oneshot))) + return res; + e->id.source = TLSL_EVENT_SOURCE_TIMER; + return 0; +} + +int tlsl_eventloop_add_fd( + struct tlsl_eventloop *el, + struct tlsl_event *e, + int fd, + uint32_t flags) +{ + int res; + + if ((res = tlsl_platform_el_add_fd(el, e, fd, flags))) + return res; + e->id.source = TLSL_EVENT_SOURCE_FD; + return 0; +} diff --git a/src/eventloop_epoll.c b/src/eventloop_epoll.c new file mode 100644 index 0000000..f861cf8 --- /dev/null +++ b/src/eventloop_epoll.c @@ -0,0 +1,191 @@ +#include +#include +#include +#include +#include + +#include "eventloop_platform.h" +#include "public/io.h" + +struct tlsl_event_platform { + int epoll_registered_fd; + bool epoll_fd_owned; +}; + +struct tlsl_eventloop_platform { + int epoll_fd; +}; + +int tlsl_platform_el_init(struct tlsl_eventloop *el) { + TLSL_DEFINE_PLATFORM_EVENTLOOP_DATA(el, elp); + + if ((elp->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) + return -1; + return 0; +} + +void tlsl_platform_el_cleanup(struct tlsl_eventloop *el) { + TLSL_DEFINE_PLATFORM_EVENTLOOP_DATA(el, elp); + + close(elp->epoll_fd); +} + +void tlsl_platform_el_event_remove(struct tlsl_eventloop *el, struct tlsl_event *e) { + TLSL_DEFINE_PLATFORM_EVENTLOOP_DATA(el, elp); + TLSL_DEFINE_PLATFORM_EVENT_DATA(e, ep); + + epoll_ctl(elp->epoll_fd, EPOLL_CTL_DEL, ep->epoll_registered_fd, NULL); +} + +void tlsl_platform_el_event_cleanup(struct tlsl_eventloop *el, struct tlsl_event *e) { + TLSL_DEFINE_PLATFORM_EVENT_DATA(e, ep); + + (void)el; + + if (ep->epoll_fd_owned && (ep->epoll_registered_fd >= 0)) { + close(ep->epoll_registered_fd); + ep->epoll_registered_fd = -1; + } +} + +static int clear_timerfd(int fd) { + uint64_t tfd_buf; + ssize_t res; + + do { + res = read(fd, &tfd_buf, sizeof(tfd_buf)); + } while ((res < 0) && (errno == EINTR)); + + if (res < 0) + return -1; + + return 0; +} + +int tlsl_platform_el_event_clear(struct tlsl_eventloop *el, struct tlsl_event *e) { + TLSL_DEFINE_PLATFORM_EVENT_DATA(e, ep); + int res; + + (void)el; + + if ((e->id.source == TLSL_EVENT_SOURCE_TIMER) && (ep->epoll_registered_fd >= 0)) { + if ((res = clear_timerfd(ep->epoll_registered_fd))) + return res; + } + + return 0; +} + +int tlsl_platform_el_wait( + struct tlsl_eventloop *el, + tlsl_event_id_t *id_out, + uint32_t *flags_out, + int timeout_ms) +{ + TLSL_DEFINE_PLATFORM_EVENTLOOP_DATA(el, elp); + struct epoll_event ev; + struct tlsl_event *e; + int res; + + if (timeout_ms < 0) + timeout_ms = -1; + + do { + res = epoll_pwait(elp->epoll_fd, &ev, 1, timeout_ms, NULL); + } while ((res < 0) && (errno == EINTR)); + + if (res <= 0) { + if (!res) + errno = ETIMEDOUT; + return -1; + } + + e = (struct tlsl_event *)ev.data.ptr; + + *id_out = e->id; + *flags_out = 0; + + if (ev.events & EPOLLIN) + *flags_out |= TLSL_EVENT_READABLE; + if (ev.events & EPOLLOUT) + *flags_out |= TLSL_EVENT_WRITABLE; + + return 0; +} + +static int el_epoll_add( + struct tlsl_eventloop *el, + struct tlsl_event *e, + int fd, + enum tlsl_event_source source, + uint32_t events, + bool owned) +{ + TLSL_DEFINE_PLATFORM_EVENTLOOP_DATA(el, elp); + TLSL_DEFINE_PLATFORM_EVENT_DATA(e, ep); + struct epoll_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.events = events; + ev.data.fd = fd; + ev.data.ptr = e; + + if (epoll_ctl(elp->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) + return -1; + + e->id.obj = (uintptr_t)fd; + e->id.source = source; + ep->epoll_registered_fd = fd; + ep->epoll_fd_owned = owned; + + return 0; +} + +int tlsl_platform_el_add_timer( + struct tlsl_eventloop *el, + struct tlsl_event *e, + uint64_t millis, + bool oneshot) +{ + FD_SCOPED(tfd); + struct itimerspec spec; + struct timespec ts; + int res; + + ts.tv_sec = (long)(millis / 1000); + ts.tv_nsec = (long)((millis % 1000) * 1000000); + + memset(&spec, 0, sizeof(spec)); + spec.it_value = ts; + if (!oneshot) + spec.it_interval = ts; + + if ((tfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC)) < 0) + return -1; + + if (timerfd_settime(tfd, 0, &spec, NULL)) + return -1; + + if ((res = el_epoll_add(el, e, tfd, TLSL_EVENT_SOURCE_TIMER, EPOLLIN, true))) + return res; + + FD_RELEASE(&tfd); + return 0; +} + +int tlsl_platform_el_add_fd( + struct tlsl_eventloop *el, + struct tlsl_event *e, + int fd, + uint32_t flags) +{ + uint32_t events; + + events = 0; + if (flags & TLSL_EVENT_READABLE) + events |= EPOLLIN; + if (flags & TLSL_EVENT_WRITABLE) + events |= EPOLLOUT; + + return el_epoll_add(el, e, fd, TLSL_EVENT_SOURCE_FD, events, false); +} diff --git a/src/eventloop_kqueue.c b/src/eventloop_kqueue.c new file mode 100644 index 0000000..8284a64 --- /dev/null +++ b/src/eventloop_kqueue.c @@ -0,0 +1,242 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "eventloop_platform.h" +#include "public/io.h" + +#define TLSL_EVENTLOOP_KQUEUE_FILTERS_MAX 2 + +struct tlsl_event_platform { + uintptr_t kqueue_ident; + uint16_t kqueue_filters[TLSL_EVENTLOOP_KQUEUE_FILTERS_MAX]; + uint8_t kqueue_filters_len; +}; + +struct tlsl_eventloop_platform { + uintptr_t kqueue_timer_next; + int kqueue_fd; +}; + +struct el_kqueue_add_params { + uint16_t filter; + uint16_t flags; + int64_t data; +}; + +static int new_kqueue(void) { +#if defined(TLSL_PLATFORM_FREEBSD) + return kqueuex(KQUEUE_CLOEXEC); + +#elif defined(TLSL_PLATFORM_OPENBSD) || defined(TLSL_PLATFORM_NETBSD) + return kqueue1(O_CLOEXEC); + +#elif defined(TLSL_PLATFORM_DARWIN) + // Darwin / XNU sets cloexec by default: + // - https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/kern/kern_event.c#L3029 + // - https://github.com/apple-oss-distributions/xnu/blob/f6217f891ac0bb64f3d375211650a4c1ff8ca1ea/bsd/kern/kern_event.c#L3070 + return kqueue(); + +#else +#error unsupported platform for kqueue-based eventloop +#endif +} + +int tlsl_platform_el_init(struct tlsl_eventloop *el) { + TLSL_DEFINE_PLATFORM_EVENTLOOP_DATA(el, elp); + + if ((elp->kqueue_fd = new_kqueue()) < 0) + return -1; + elp->kqueue_timer_next = 0; + return 0; +} + +void tlsl_platform_el_cleanup(struct tlsl_eventloop *el) { + TLSL_DEFINE_PLATFORM_EVENTLOOP_DATA(el, elp); + + close(elp->kqueue_fd); +} + +void tlsl_platform_el_event_remove(struct tlsl_eventloop *el, struct tlsl_event *e) { + TLSL_DEFINE_PLATFORM_EVENTLOOP_DATA(el, elp); + TLSL_DEFINE_PLATFORM_EVENT_DATA(e, ep); + struct kevent kevs[TLSL_EVENTLOOP_KQUEUE_FILTERS_MAX]; + unsigned i; + unsigned filters_len; + + if ((filters_len = ep->kqueue_filters_len) > TLSL_EVENTLOOP_KQUEUE_FILTERS_MAX) + filters_len = TLSL_EVENTLOOP_KQUEUE_FILTERS_MAX; + + if (!filters_len) + return; + + memset(kevs, 0, sizeof(kevs)); + for (i = 0; i < filters_len; i++) { + kevs[i].ident = ep->kqueue_ident; + kevs[i].filter = ep->kqueue_filters[i]; + kevs[i].flags = EV_DELETE; + } + + kevent(elp->kqueue_fd, kevs, filters_len, NULL, 0, NULL); +} + +void tlsl_platform_el_event_cleanup(struct tlsl_eventloop *el, struct tlsl_event *e) { + (void)el; + (void)e; +} + +int tlsl_platform_el_event_clear(struct tlsl_eventloop *el, struct tlsl_event *e) { + (void)el; + (void)e; + return 0; +} + +int tlsl_platform_el_wait( + struct tlsl_eventloop *el, + tlsl_event_id_t *id_out, + uint32_t *flags_out, + int timeout_ms) +{ + TLSL_DEFINE_PLATFORM_EVENTLOOP_DATA(el, elp); + struct kevent kev; + struct tlsl_event *e; + struct timespec timeout; + struct timespec *timeout_ptr; + int res; + + if (timeout_ms >= 0) { + timeout.tv_sec = timeout_ms / 1000; + timeout.tv_nsec = (long)(timeout_ms % 1000) * 1000000; + timeout_ptr = &timeout; + } + else + timeout_ptr = NULL; + + do { + res = kevent(elp->kqueue_fd, NULL, 0, &kev, 1, timeout_ptr); + } while ((res < 0) && (errno == EINTR)); + + if (res <= 0) { + if (!res) + errno = ETIMEDOUT; + return -1; + } + + e = (struct tlsl_event *)kev.udata; + + *id_out = e->id; + *flags_out = 0; + + switch (kev.filter) { + case EVFILT_TIMER: + case EVFILT_READ: + *flags_out |= TLSL_EVENT_READABLE; + break; + case EVFILT_WRITE: + *flags_out |= TLSL_EVENT_WRITABLE; + break; + default: + break; + } + + return 0; +} + +static int el_kqueue_add( + struct tlsl_eventloop *el, + struct tlsl_event *e, + uintptr_t ident, + enum tlsl_event_source source, + const struct el_kqueue_add_params *params, + uint8_t params_len) +{ + TLSL_DEFINE_PLATFORM_EVENTLOOP_DATA(el, elp); + TLSL_DEFINE_PLATFORM_EVENT_DATA(e, ep); + struct kevent kevs[TLSL_EVENTLOOP_KQUEUE_FILTERS_MAX]; + unsigned i; + + if (!params_len || (params_len > TLSL_EVENTLOOP_KQUEUE_FILTERS_MAX)) { + errno = EINVAL; + return -1; + } + + memset(kevs, 0, sizeof(kevs)); + for (i = 0; i < params_len; i++) { + kevs[i].ident = ident; + kevs[i].filter = params[i].filter; + kevs[i].flags = EV_ADD | params[i].flags; + kevs[i].data = params[i].data; + kevs[i].udata = e; + } + + if (kevent(elp->kqueue_fd, kevs, params_len, NULL, 0, NULL)) + return -1; + + e->id.obj = ident; + e->id.source = source; + ep->kqueue_ident = ident; + ep->kqueue_filters_len = params_len; + for (i = 0; i < params_len; i++) + ep->kqueue_filters[i] = params[i].filter; + + return 0; +} + +int tlsl_platform_el_add_timer( + struct tlsl_eventloop *el, + struct tlsl_event *e, + uint64_t millis, + bool oneshot) +{ + TLSL_DEFINE_PLATFORM_EVENTLOOP_DATA(el, elp); + struct el_kqueue_add_params params; + int res; + + params.filter = EVFILT_TIMER; + params.data = millis; + + params.flags = 0; + if (oneshot) + params.flags |= EV_ONESHOT; + + if ((res = el_kqueue_add(el, e, elp->kqueue_timer_next, TLSL_EVENT_SOURCE_TIMER, ¶ms, 1))) + return res; + + elp->kqueue_timer_next++; + + return 0; +} + +int tlsl_platform_el_add_fd( + struct tlsl_eventloop *el, + struct tlsl_event *e, + int fd, + uint32_t flags) +{ + struct el_kqueue_add_params params[2]; + uint8_t params_len; + + params_len = 0; + + if (flags & TLSL_EVENT_READABLE) { + params[params_len++] = (struct el_kqueue_add_params) { + .filter = EVFILT_READ, + .flags = 0, + .data = 0, + }; + } + + if (flags & TLSL_EVENT_WRITABLE) { + params[params_len++] = (struct el_kqueue_add_params) { + .filter = EVFILT_WRITE, + .flags = 0, + .data = 0, + }; + } + + return el_kqueue_add(el, e, (uintptr_t)fd, TLSL_EVENT_SOURCE_FD, params, params_len); +} diff --git a/src/str.c b/src/str.c new file mode 100644 index 0000000..f5883c5 --- /dev/null +++ b/src/str.c @@ -0,0 +1,18 @@ +#include +#include + +#include "public/str.h" + +str_buf_t sbuf_from_sview(str_view_t s) { + char *buf; + + if (sview_is_null(s) || sview_is_empty(s)) + return SBUF_NULL; + + if (!(buf = malloc(s.len + 1))) + return SBUF_NULL; + + memcpy(buf, s.ptr, s.len); + buf[s.len] = 0; + return (str_buf_t) { buf, s.len }; +}