1/* MIT License
2 *
3 * Copyright (c) 2024 Brad House
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the next
13 * paragraph) shall be included in all copies or substantial portions of the
14 * Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 *
24 * SPDX-License-Identifier: MIT
25 */
26#include "ares_setup.h"
27#include "ares.h"
28#include "ares_private.h"
29#include "ares_event.h"
30
31#ifdef HAVE_SYS_TYPES_H
32#  include <sys/types.h>
33#endif
34#ifdef HAVE_SYS_EVENT_H
35#  include <sys/event.h>
36#endif
37#ifdef HAVE_SYS_TIME_H
38#  include <sys/time.h>
39#endif
40#ifdef HAVE_FCNTL_H
41#  include <fcntl.h>
42#endif
43
44#ifdef HAVE_KQUEUE
45
46typedef struct {
47  int            kqueue_fd;
48  struct kevent *changelist;
49  size_t         nchanges;
50  size_t         nchanges_alloc;
51} ares_evsys_kqueue_t;
52
53static void ares_evsys_kqueue_destroy(ares_event_thread_t *e)
54{
55  ares_evsys_kqueue_t *kq = NULL;
56
57  if (e == NULL) {
58    return;
59  }
60
61  kq = e->ev_sys_data;
62  if (kq == NULL) {
63    return;
64  }
65
66  if (kq->kqueue_fd != -1) {
67    close(kq->kqueue_fd);
68  }
69
70  ares_free(kq->changelist);
71  ares_free(kq);
72  e->ev_sys_data = NULL;
73}
74
75static ares_bool_t ares_evsys_kqueue_init(ares_event_thread_t *e)
76{
77  ares_evsys_kqueue_t *kq = NULL;
78
79  kq = ares_malloc_zero(sizeof(*kq));
80  if (kq == NULL) {
81    return ARES_FALSE;
82  }
83
84  e->ev_sys_data = kq;
85
86  kq->kqueue_fd = kqueue();
87  if (kq->kqueue_fd == -1) {
88    ares_evsys_kqueue_destroy(e);
89    return ARES_FALSE;
90  }
91
92#  ifdef FD_CLOEXEC
93  fcntl(kq->kqueue_fd, F_SETFD, FD_CLOEXEC);
94#  endif
95
96  kq->nchanges_alloc = 8;
97  kq->changelist =
98    ares_malloc_zero(sizeof(*kq->changelist) * kq->nchanges_alloc);
99  if (kq->changelist == NULL) {
100    ares_evsys_kqueue_destroy(e);
101    return ARES_FALSE;
102  }
103
104  e->ev_signal = ares_pipeevent_create(e);
105  if (e->ev_signal == NULL) {
106    ares_evsys_kqueue_destroy(e);
107    return ARES_FALSE;
108  }
109
110  return ARES_TRUE;
111}
112
113static void ares_evsys_kqueue_enqueue(ares_evsys_kqueue_t *kq, int fd,
114                                      int16_t filter, uint16_t flags)
115{
116  size_t idx;
117
118  if (kq == NULL) {
119    return;
120  }
121
122  idx = kq->nchanges;
123
124  kq->nchanges++;
125
126  if (kq->nchanges > kq->nchanges_alloc) {
127    kq->nchanges_alloc <<= 1;
128    kq->changelist = ares_realloc_zero(kq->changelist, kq->nchanges_alloc >> 1,
129                                       kq->nchanges_alloc);
130  }
131
132  EV_SET(&kq->changelist[idx], fd, filter, flags, 0, 0, 0);
133}
134
135static void ares_evsys_kqueue_event_process(ares_event_t      *event,
136                                            ares_event_flags_t old_flags,
137                                            ares_event_flags_t new_flags)
138{
139  ares_event_thread_t *e = event->e;
140  ares_evsys_kqueue_t *kq;
141
142  if (e == NULL) {
143    return;
144  }
145
146  kq = e->ev_sys_data;
147  if (kq == NULL) {
148    return;
149  }
150
151  if (new_flags & ARES_EVENT_FLAG_READ && !(old_flags & ARES_EVENT_FLAG_READ)) {
152    ares_evsys_kqueue_enqueue(kq, event->fd, EVFILT_READ, EV_ADD | EV_ENABLE);
153  }
154
155  if (!(new_flags & ARES_EVENT_FLAG_READ) && old_flags & ARES_EVENT_FLAG_READ) {
156    ares_evsys_kqueue_enqueue(kq, event->fd, EVFILT_READ, EV_DELETE);
157  }
158
159  if (new_flags & ARES_EVENT_FLAG_WRITE &&
160      !(old_flags & ARES_EVENT_FLAG_WRITE)) {
161    ares_evsys_kqueue_enqueue(kq, event->fd, EVFILT_WRITE, EV_ADD | EV_ENABLE);
162  }
163
164  if (!(new_flags & ARES_EVENT_FLAG_WRITE) &&
165      old_flags & ARES_EVENT_FLAG_WRITE) {
166    ares_evsys_kqueue_enqueue(kq, event->fd, EVFILT_WRITE, EV_DELETE);
167  }
168}
169
170static ares_bool_t ares_evsys_kqueue_event_add(ares_event_t *event)
171{
172  ares_evsys_kqueue_event_process(event, 0, event->flags);
173  return ARES_TRUE;
174}
175
176static void ares_evsys_kqueue_event_del(ares_event_t *event)
177{
178  ares_evsys_kqueue_event_process(event, event->flags, 0);
179}
180
181static void ares_evsys_kqueue_event_mod(ares_event_t      *event,
182                                        ares_event_flags_t new_flags)
183{
184  ares_evsys_kqueue_event_process(event, event->flags, new_flags);
185}
186
187static size_t ares_evsys_kqueue_wait(ares_event_thread_t *e,
188                                     unsigned long        timeout_ms)
189{
190  struct kevent        events[8];
191  size_t               nevents = sizeof(events) / sizeof(*events);
192  ares_evsys_kqueue_t *kq      = e->ev_sys_data;
193  int                  rv;
194  size_t               i;
195  struct timespec      ts;
196  struct timespec     *timeout = NULL;
197  size_t               cnt     = 0;
198
199  if (timeout_ms != 0) {
200    ts.tv_sec  = timeout_ms / 1000;
201    ts.tv_nsec = (timeout_ms % 1000) * 1000 * 1000;
202    timeout    = &ts;
203  }
204
205  memset(events, 0, sizeof(events));
206
207  rv = kevent(kq->kqueue_fd, kq->changelist, (int)kq->nchanges, events,
208              (int)nevents, timeout);
209  if (rv < 0) {
210    return 0;
211  }
212
213  /* Changelist was consumed */
214  kq->nchanges = 0;
215  nevents      = (size_t)rv;
216
217  for (i = 0; i < nevents; i++) {
218    ares_event_t      *ev;
219    ares_event_flags_t flags = 0;
220
221    ev = ares__htable_asvp_get_direct(e->ev_handles,
222                                      (ares_socket_t)events[i].ident);
223    if (ev == NULL || ev->cb == NULL) {
224      continue;
225    }
226
227    cnt++;
228
229    if (events[i].filter == EVFILT_READ ||
230        events[i].flags & (EV_EOF | EV_ERROR)) {
231      flags |= ARES_EVENT_FLAG_READ;
232    } else {
233      flags |= ARES_EVENT_FLAG_WRITE;
234    }
235
236    ev->cb(e, ev->fd, ev->data, flags);
237  }
238
239  return cnt;
240}
241
242const ares_event_sys_t ares_evsys_kqueue = { "kqueue",
243                                             ares_evsys_kqueue_init,
244                                             ares_evsys_kqueue_destroy,
245                                             ares_evsys_kqueue_event_add,
246                                             ares_evsys_kqueue_event_del,
247                                             ares_evsys_kqueue_event_mod,
248                                             ares_evsys_kqueue_wait };
249#endif
250