Seregon/zftpd

Zero-copy FTP/HTTP Daemon compatible with all POSIX systems

C/11.0 KB/No license
src/event_loop_kqueue.c
zftpd / src / event_loop_kqueue.c
1/*
2MIT License
3 
4Copyright (c) 2026 Seregon
5 
6Permission is hereby granted, free of charge, to any person obtaining a copy
7of this software and associated documentation files (the "Software"), to deal
8in the Software without restriction, including without limitation the rights
9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10copies of the Software, and to permit persons to whom the Software is
11furnished to do so, subject to the following conditions:
12 
13The above copyright notice and this permission notice shall be included in all
14copies or substantial portions of the Software.
15 
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22SOFTWARE.
23*/
24/**
25 * @file event_loop_kqueue.c
26 * @brief Event loop implementation using kqueue (macOS/FreeBSD/PS4/PS5)
27 *
28 * ARCHITECTURE:
29 *
30 * kqueue fd
31 * │
32 * ├── EVFILT_READ fd=listen ──► accept callback
33 * ├── EVFILT_READ fd=client1 ──► client callback
34 * ├── EVFILT_READ fd=client2 ──► client callback
35 * └── ...
36 *
37 * Each monitored fd gets a handler_entry_t that stores both the
38 * callback and user data, since kqueue only has one udata slot.
39 */
40 
41#include "event_loop.h"
42#include <errno.h>
43#include <string.h>
44#include <sys/types.h>
45#include <sys/event.h>
46#include <sys/time.h>
47#include <unistd.h>
48 
49#define MAX_EVENTS 1024
50#define MAX_HANDLERS 1024
51 
52/*===========================================================================*
53 * HANDLER TABLE — maps fd → {callback, data}
54 *===========================================================================*/
55 
56typedef struct {
57 int fd; /**< File descriptor (-1 = unused) */
58 event_callback_t callback; /**< User callback */
59 void *data; /**< User context */
60} handler_entry_t;
61 
62struct event_loop {
63 int kq;
64 int running;
65 struct kevent *events;
66 size_t max_events;
67 
68 handler_entry_t handlers[MAX_HANDLERS];
69 size_t handler_count;
70};
71 
72static event_loop_t g_event_loop;
73static struct kevent g_event_storage[MAX_EVENTS];
74static int g_event_loop_in_use = 0;
75 
76/*===========================================================================*
77 * HANDLER TABLE HELPERS
78 *===========================================================================*/
79 
80static handler_entry_t *find_handler(event_loop_t *loop, int fd) {
81 for (size_t i = 0; i < loop->handler_count; i++) {
82 if (loop->handlers[i].fd == fd) {
83 return &loop->handlers[i];
84 }
85 }
86 return NULL;
87}
88 
89static handler_entry_t *add_handler(event_loop_t *loop, int fd,
90 event_callback_t cb, void *data) {
91 /* Check if already exists */
92 handler_entry_t *h = find_handler(loop, fd);
93 if (h != NULL) {
94 h->callback = cb;
95 h->data = data;
96 return h;
97 }
98 
99 /* Find empty slot or append */
100 for (size_t i = 0; i < MAX_HANDLERS; i++) {
101 if (loop->handlers[i].fd < 0) {
102 loop->handlers[i].fd = fd;
103 loop->handlers[i].callback = cb;
104 loop->handlers[i].data = data;
105 if (i >= loop->handler_count) {
106 loop->handler_count = i + 1;
107 }
108 return &loop->handlers[i];
109 }
110 }
111 
112 return NULL; /* table full */
113}
114 
115static void remove_handler(event_loop_t *loop, int fd) {
116 handler_entry_t *h = find_handler(loop, fd);
117 if (h != NULL) {
118 h->fd = -1;
119 h->callback = NULL;
120 h->data = NULL;
121 }
122}
123 
124/*===========================================================================*
125 * CREATE / DESTROY
126 *===========================================================================*/
127 
128event_loop_t *event_loop_create(void) {
129 if (g_event_loop_in_use != 0) {
130 return &g_event_loop;
131 }
132 
133 event_loop_t *loop = &g_event_loop;
134 memset(loop, 0, sizeof(*loop));
135 
136 loop->kq = kqueue();
137 if (loop->kq < 0) {
138 return NULL;
139 }
140 
141 loop->max_events = MAX_EVENTS;
142 loop->events = g_event_storage;
143 
144 /* Initialize all handler slots as unused */
145 for (size_t i = 0; i < MAX_HANDLERS; i++) {
146 loop->handlers[i].fd = -1;
147 }
148 loop->handler_count = 0;
149 loop->running = 0;
150 
151 g_event_loop_in_use = 1;
152 return loop;
153}
154 
155void event_loop_destroy(event_loop_t *loop) {
156 if ((loop == NULL) || (loop != &g_event_loop)) {
157 return;
158 }
159 if (loop->kq >= 0) {
160 close(loop->kq);
161 }
162 loop->kq = -1;
163 loop->running = 0;
164 loop->events = NULL;
165 loop->max_events = 0;
166 loop->handler_count = 0;
167 for (size_t i = 0; i < MAX_HANDLERS; i++) {
168 loop->handlers[i].fd = -1;
169 loop->handlers[i].callback = NULL;
170 loop->handlers[i].data = NULL;
171 }
172 g_event_loop_in_use = 0;
173}
174 
175/*===========================================================================*
176 * ADD / MODIFY / REMOVE
177 *===========================================================================*/
178 
179int event_loop_add(event_loop_t *loop, int fd, uint32_t events,
180 event_callback_t callback, void *data) {
181 if (loop == NULL || fd < 0 || callback == NULL) {
182 return -1;
183 }
184 
185 handler_entry_t *h = add_handler(loop, fd, callback, data);
186 if (h == NULL) {
187 return -1;
188 }
189 
190 if (events & EVENT_READ) {
191 struct kevent kev;
192 EV_SET(&kev, fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, h);
193 if (kevent(loop->kq, &kev, 1, NULL, 0, NULL) < 0) {
194 return -1;
195 }
196 }
197 
198 if (events & EVENT_WRITE) {
199 struct kevent kev;
200 EV_SET(&kev, fd, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, h);
201 if (kevent(loop->kq, &kev, 1, NULL, 0, NULL) < 0) {
202 return -1;
203 }
204 }
205 
206 return 0;
207}
208 
209int event_loop_modify(event_loop_t *loop, int fd, uint32_t events) {
210 if (loop == NULL || fd < 0) {
211 return -1;
212 }
213 
214 handler_entry_t *h = find_handler(loop, fd);
215 if (h == NULL) {
216 return -1;
217 }
218 
219 return event_loop_add(loop, fd, events, h->callback, h->data);
220}
221 
222int event_loop_remove(event_loop_t *loop, int fd) {
223 if (loop == NULL || fd < 0) {
224 return -1;
225 }
226 
227 struct kevent kev[2];
228 EV_SET(&kev[0], fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
229 EV_SET(&kev[1], fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
230 
231 /* Ignore errors (filter may not exist) */
232 (void)kevent(loop->kq, kev, 2, NULL, 0, NULL);
233 
234 remove_handler(loop, fd);
235 return 0;
236}
237 
238/*===========================================================================*
239 * RUN
240 *===========================================================================*/
241 
242int event_loop_run(event_loop_t *loop) {
243 if (loop == NULL) {
244 return -1;
245 }
246 
247 loop->running = 1;
248 
249 while (loop->running) {
250 struct timespec timeout = {.tv_sec = 1, .tv_nsec = 0};
251 
252 int nev = kevent(loop->kq, NULL, 0, loop->events, (int)loop->max_events,
253 &timeout);
254 
255 if (nev < 0) {
256 if (errno == EINTR) {
257 continue;
258 }
259 return -1;
260 }
261 
262 for (int i = 0; i < nev; i++) {
263 struct kevent *kev = &loop->events[i];
264 handler_entry_t *h = (handler_entry_t *)kev->udata;
265 
266 if (h == NULL || h->callback == NULL) {
267 continue;
268 }
269 
270 uint32_t ev = 0;
271 if (kev->filter == EVFILT_READ)
272 ev |= EVENT_READ;
273 if (kev->filter == EVFILT_WRITE)
274 ev |= EVENT_WRITE;
275 if (kev->flags & EV_EOF)
276 ev |= EVENT_CLOSE;
277 if (kev->flags & EV_ERROR)
278 ev |= EVENT_ERROR;
279 
280 int ret = h->callback((int)kev->ident, ev, h->data);
281 if (ret < 0) {
282 event_loop_remove(loop, (int)kev->ident);
283 }
284 }
285 }
286 
287 return 0;
288}
289 
290void event_loop_stop(event_loop_t *loop) {
291 if (loop != NULL) {
292 loop->running = 0;
293 }
294}
295