* Copyright 2023, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include <sys/event.h>
#include <StackOrHeapArray.h>
#include <libroot/errno_private.h>
#include <libroot/time_private.h>
#include <syscalls.h>
#include <event_queue_defs.h>
extern "C" int
kqueue()
{
int fd = _kern_event_queue_create(O_CLOFORK);
if (fd < 0) {
__set_errno(fd);
return -1;
}
return fd;
}
static short
filter_from_info(const event_wait_info& info)
{
switch (info.type) {
case B_OBJECT_TYPE_FD:
if (info.events > 0 && (info.events & B_EVENT_WRITE) != 0)
return EVFILT_WRITE;
return EVFILT_READ;
case B_OBJECT_TYPE_THREAD:
return EVFILT_PROC;
}
return 0;
}
extern "C" int
kevent(int kq,
const struct kevent *changelist, int nchanges,
struct kevent *eventlist, int nevents,
const struct timespec *tspec)
{
BStackOrHeapArray<event_wait_info, 16> waitInfos(max_c(nchanges, nevents));
event_wait_info* waitInfo = waitInfos;
int changedInfos = 0;
for (int i = 0; i < nchanges; i++) {
waitInfo->object = changelist[i].ident;
waitInfo->events = 0;
waitInfo->user_data = changelist[i].udata;
int32 events = 0, behavior = 0;
switch (changelist[i].filter) {
case EVFILT_READ:
waitInfo->type = B_OBJECT_TYPE_FD;
events = B_EVENT_READ;
break;
case EVFILT_WRITE:
waitInfo->type = B_OBJECT_TYPE_FD;
events = B_EVENT_WRITE;
break;
case EVFILT_PROC:
waitInfo->type = B_OBJECT_TYPE_THREAD;
if ((changelist[i].fflags & NOTE_EXIT) != 0)
events |= B_EVENT_INVALID;
break;
default:
return EINVAL;
}
if ((changelist[i].flags & EV_ONESHOT) != 0)
behavior |= B_EVENT_ONE_SHOT;
if ((changelist[i].flags & EV_CLEAR) == 0)
behavior |= B_EVENT_LEVEL_TRIGGERED;
if (changelist[i].filter == EVFILT_READ || changelist[i].filter == EVFILT_WRITE) {
if ((changelist[i].flags & EV_ONESHOT) != 0) {
__set_errno(EOPNOTSUPP);
return -1;
}
const short otherFilter = (changelist[i].filter == EVFILT_READ)
? EVFILT_WRITE : EVFILT_READ;
const int32 otherEvents = (otherFilter == EVFILT_READ)
? B_EVENT_READ : B_EVENT_WRITE;
int j;
for (j = 0; j < nchanges; j++) {
if (changelist[j].ident != changelist[i].ident)
continue;
if (changelist[j].filter != otherFilter)
continue;
break;
}
if (j < nchanges) {
if (j < i) {
continue;
}
if ((changelist[j].flags & EV_ADD) != 0) {
waitInfo->events |= otherEvents;
} else if ((changelist[j].flags & EV_DELETE) != 0) {
waitInfo->events &= ~otherEvents;
}
} else {
event_wait_info info;
info.type = B_OBJECT_TYPE_FD;
info.object = waitInfo->object;
info.events = -1;
status_t status = _kern_event_queue_select(kq, &info, 1);
if (status == B_OK)
waitInfo->events |= (info.events & otherEvents);
}
}
if ((changelist[i].flags & EV_ADD) != 0) {
waitInfo->events |= events;
} else if ((changelist[i].flags & EV_DELETE) != 0) {
waitInfo->events &= ~events;
}
if (waitInfo->events != 0)
waitInfo->events |= behavior;
changedInfos++;
waitInfo++;
}
if (changedInfos != 0) {
status_t status = _kern_event_queue_select(kq, waitInfos, changedInfos);
if (status != B_OK) {
if (nchanges == 1 && nevents == 0) {
__set_errno(waitInfos[0].events);
return -1;
}
int errors = 0;
for (int i = 0; i < changedInfos; i++) {
if (waitInfos[i].events > 0)
continue;
if (nevents == 0) {
errors = -1;
break;
}
short filter = filter_from_info(waitInfos[i]);
int64_t data = waitInfos[i].events;
EV_SET(eventlist, waitInfos[i].object,
filter, EV_ERROR, 0, data, waitInfos[i].user_data);
eventlist++;
nevents--;
errors++;
}
if (errors > 0)
return errors;
__set_errno(status);
return -1;
}
}
if (nevents != 0) {
bigtime_t timeout = 0;
uint32 waitFlags = 0;
if (tspec != NULL) {
if (!timespec_to_bigtime(*tspec, timeout)) {
__set_errno(EINVAL);
return -1;
}
waitFlags |= B_RELATIVE_TIMEOUT;
}
ssize_t events = _kern_event_queue_wait(kq, waitInfos,
max_c(1, nevents / 2), waitFlags, timeout);
if (events > 0) {
int returnedEvents = 0;
for (ssize_t i = 0; i < events; i++) {
unsigned short flags = 0;
unsigned int fflags = 0;
int64_t data = 0;
if (waitInfos[i].events < 0) {
flags |= EV_ERROR;
data = waitInfos[i].events;
} else if ((waitInfos[i].events & B_EVENT_DISCONNECTED) != 0) {
flags |= EV_EOF;
} else if ((waitInfos[i].events & B_EVENT_INVALID) != 0) {
switch (waitInfos[i].type) {
case B_OBJECT_TYPE_FD:
flags |= EV_EOF;
break;
case B_OBJECT_TYPE_THREAD: {
fflags |= NOTE_EXIT;
status_t returnValue = -1;
status_t status = wait_for_thread(waitInfos[i].object, &returnValue);
if (status == B_OK)
data = returnValue;
else
data = -1;
break;
}
}
} else if ((waitInfos[i].events & B_EVENT_ERROR) != 0) {
flags |= EV_ERROR;
data = EINVAL;
}
short filter = filter_from_info(waitInfos[i]);
if (waitInfos[i].type == B_OBJECT_TYPE_FD && (flags & (EV_ERROR | EV_EOF)) == 0) {
if ((waitInfos[i].events & (B_EVENT_READ | B_EVENT_WRITE))
== (B_EVENT_READ | B_EVENT_WRITE)) {
if (nevents > 1) {
EV_SET(eventlist, waitInfos[i].object,
EVFILT_WRITE, flags, fflags, data, waitInfos[i].user_data);
eventlist++;
returnedEvents++;
nevents--;
}
filter = EVFILT_READ;
}
}
EV_SET(eventlist, waitInfos[i].object,
filter, flags, fflags, data, waitInfos[i].user_data);
eventlist++;
returnedEvents++;
nevents--;
}
return returnedEvents;
} else if (events < 0) {
if (events == B_WOULD_BLOCK || events == B_TIMED_OUT)
return 0;
__set_errno(events);
return -1;
}
return 0;
}
return 0;
}