/* * Copyright 2023, Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. */ #include #include #include #include #include #include extern "C" int kqueue() { int fd = _kern_event_queue_create(0); 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 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) { // kqueue treats the same file descriptor with both READ and WRITE filters // as two separate listeners. Haiku, however, treats it as one. // We rectify this here by carefully combining the two. // We can't support ONESHOT for descriptors due to the separation. 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; // First, check if the other filter is specified in this changelist. int j; for (j = 0; j < nchanges; j++) { if (changelist[j].ident != changelist[i].ident) continue; if (changelist[j].filter != otherFilter) continue; // We've found it. break; } if (j < nchanges) { // It is in the list. if (j < i) { // And it's already been taken care of. continue; } // Fold it into this one. if ((changelist[j].flags & EV_ADD) != 0) { waitInfo->events |= otherEvents; } else if ((changelist[j].flags & EV_DELETE) != 0) { waitInfo->events &= ~otherEvents; } } else { // It is not in the list. See if it's already set. 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) { // Special case: return the lone error directly. __set_errno(waitInfos[0].events); return -1; } // Report problems as error events. 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) { // Do we have both a read and a write event? if ((waitInfos[i].events & (B_EVENT_READ | B_EVENT_WRITE)) == (B_EVENT_READ | B_EVENT_WRITE)) { // We do. Report both, if we can. 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; }