feat(libkernel): implement kqueue and kevent over the event queue

Cloudberry Kingdom (PPSA23827, #3177) calls kqueue right after start-up
and stopped on the throwing declarations merged with #3337; Palworld's
Discord module (PPSA23126, #2648) imports kevent.

kqueue reserves a host null-device descriptor in the file descriptor
space and registers it as an event queue that reports each ready event
once per call and moves level-triggered ones behind the others, as
kqueue_scan does. close and sceKernelClose close the queue and drop the
descriptor's events from every queue, dup2 drops them for the target,
and dup/dup2 of a kqueue throw, so a reused descriptor number is never
read as a stale kqueue.

kevent follows FreeBSD 11 kern_event.c (release/11.0.1 34bd3475):
kqueue_kevent for the changelist (filter 0 skipped, EV_SYSFLAGS
cleared, EV_ERROR receipts with the errno in data, EV_RECEIPT, the
early return when nevents is exhausted), kqueue_register for EV_ADD,
EV_DELETE, EV_ENABLE, EBADF, ENOENT and EINVAL, kqueue_scan for the
timeout (EINVAL for a bad timespec, zero polls, above INT32_MAX waits
forever) and filt_timerattach, filt_usertouch for the timer and user
filters. Read and write events reuse the guest socket poll of
sceKernelAddReadEvent/WriteEvent.

Throws: other filters, EV_DISABLE, EV_DISPATCH, EV_FORCEONESHOT,
NOTE_LOWAT, EV_CLEAR on a socket, read/write events on non-socket
descriptors or another kqueue, changes to an existing timer (11.0 and
11.4 differ), a NOTE_NSECONDS fraction above one second and a negative
nevents. Recorded in TechnicalDebt.

Test guest_kqueue covers creation and close, timers, a user event
triggered from another thread, read and write events on a socket pair,
EV_ERROR receipts, the timeout and the error cases.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
David 2026-10-11 12:35:43 +02:00 committed by Adrià Franch
parent 27873b0f74
commit 9ef4aa2c40
8 changed files with 788 additions and 53 deletions

View File

@ -4,6 +4,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/Equeue/Equeue.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Equeue/src/EventAccessors.cpp
${CMAKE_CURRENT_SOURCE_DIR}/File/src/DescriptorControl.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Equeue/src/Kqueue.cpp
${CMAKE_CURRENT_SOURCE_DIR}/File/src/DirectoryDescriptor.cpp
${CMAKE_CURRENT_SOURCE_DIR}/File/src/FileLock.cpp
${CMAKE_CURRENT_SOURCE_DIR}/File/src/NativeStat.cpp
@ -1021,6 +1022,16 @@ if(BUILD_TESTING)
set_tests_properties(guest_dup PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_kqueue_tests ${LIBS_INCLUDE_DIR}/tests/GuestKqueue.cpp)
target_include_directories(guest_kqueue_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_kqueue_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_kqueue_tests)
add_test(NAME guest_kqueue COMMAND guest_kqueue_tests)
set_tests_properties(guest_kqueue PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_kqueue PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_socket_event_tests ${LIBS_INCLUDE_DIR}/tests/GuestSocketEvents.cpp)
target_include_directories(guest_socket_event_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_socket_event_tests PRIVATE libkernel libc)

View File

@ -16,7 +16,7 @@ static uint64_t g_nextEqueue = 1;
extern "C" {
KernelEqueuePrivate::KernelEqueuePrivate(KernelEqueue handle) : m_handle(handle) {}
KernelEqueuePrivate::KernelEqueuePrivate(KernelEqueue handle, bool reportOncePerWait) : m_handle(handle), m_reportOncePerWait(reportOncePerWait) {}
KernelEqueuePrivate::~KernelEqueuePrivate() {
Close();
@ -113,17 +113,10 @@ void KernelEqueuePrivate::WaitForDescriptors(std::unique_lock<std::mutex>& lock,
}
}
int KernelEqueuePrivate::GetTriggeredEvents(KernelEvent* ev, int num) {
std::unique_lock lock(m_mutex);
if (m_closed) {
return SCE_KERNEL_ERROR_EBADF;
}
TriggerExpiredTimers(MonotonicNs());
PollEvents();
return CollectTriggered(ev, num);
}
int KernelEqueuePrivate::CollectTriggered(KernelEvent* ev, int num) {
if (m_reportOncePerWait) {
return CollectOncePerWait(ev, num);
}
int ret = 0;
for (auto it = m_events.begin(); it != m_events.end() && ret < num;) {
auto& e = *it;
@ -153,12 +146,60 @@ int KernelEqueuePrivate::CollectTriggered(KernelEvent* ev, int num) {
return ret;
}
int KernelEqueuePrivate::WaitForEvents(KernelEvent* ev, int num, uint32_t micros) {
int KernelEqueuePrivate::CollectOncePerWait(KernelEvent* ev, int num) {
int ret = 0;
std::list<KernelEqueueEvent> requeued;
for (auto it = m_events.begin(); it != m_events.end() && ret < num;) {
auto& e = *it;
if (!e.triggered) {
++it;
continue;
}
ev[ret++] = e.event;
if ((e.event.flags & EV_ONESHOT) != 0) {
if (e.filter.deleteEventFunc != nullptr) {
auto owner = e.filter.owner;
e.filter.deleteEventFunc(m_handle, &e);
}
it = m_events.erase(it);
continue;
}
if (e.filter.resetFunc != nullptr) {
e.filter.resetFunc(&e);
} else if ((e.event.flags & EV_CLEAR) != 0) {
e.triggered = false;
e.event.fflags = 0;
e.event.data = 0;
}
const auto next = std::next(it);
if (e.triggered) {
requeued.splice(requeued.end(), m_events, it);
}
it = next;
}
m_events.splice(m_events.end(), requeued);
return ret;
}
int KernelEqueuePrivate::GetTriggeredEvents(KernelEvent* ev, int num) {
std::unique_lock lock(m_mutex);
if (m_closed) {
return SCE_KERNEL_ERROR_EBADF;
}
TriggerExpiredTimers(MonotonicNs());
PollEvents();
return CollectTriggered(ev, num);
}
int KernelEqueuePrivate::WaitForEvents(KernelEvent* ev, int num, uint32_t micros) {
return WaitForEventsUntil(ev, num, micros == 0 ? 0 : TimedWait::DeadlineNanos(micros));
}
int KernelEqueuePrivate::WaitForEventsUntil(KernelEvent* ev, int num, std::uint64_t deadline) {
std::unique_lock lock(m_mutex);
if (m_closed) {
return SCE_KERNEL_ERROR_EBADF;
}
const std::uint64_t deadline = TimedWait::DeadlineNanos(micros);
for (;;) {
TriggerExpiredTimers(MonotonicNs());
const bool polling = PollEvents();
@ -169,7 +210,7 @@ int KernelEqueuePrivate::WaitForEvents(KernelEvent* ev, int num, uint32_t micros
if (m_closed) {
return SCE_KERNEL_ERROR_EBADF;
}
if (micros == 0) {
if (deadline == 0) {
uint32_t timerWait = 0;
const bool hasTimer = NextTimerWaitMicros(MonotonicNs(), &timerWait);
const std::uint64_t wake = hasTimer ? TimedWait::NowNanos() + static_cast<std::uint64_t>(timerWait) * 1000ULL : 0;
@ -249,6 +290,32 @@ int KernelEqueuePrivate::TriggerEvent(uintptr_t ident, int16_t filter, void* tri
return EQUEUE_OK;
}
bool KernelEqueuePrivate::HasEvent(uintptr_t ident, int16_t filter) {
std::unique_lock lock(m_mutex);
return std::any_of(m_events.begin(), m_events.end(), [ident, filter](const auto& e) {
return e.event.ident == ident && e.event.filter == filter;
});
}
int KernelEqueuePrivate::EditEvent(uintptr_t ident, int16_t filter, const std::function<void(KernelEqueueEvent&)>& edit) {
std::unique_lock lock(m_mutex);
if (m_closed) {
return SCE_KERNEL_ERROR_EBADF;
}
auto it = std::find_if(m_events.begin(), m_events.end(),
[ident, filter](const auto& e) {
return e.event.ident == ident && e.event.filter == filter;
}
);
if (it == m_events.end()) {
return SCE_KERNEL_ERROR_ENOENT;
}
edit(*it);
m_cond.NotifyOne();
WakePollers();
return EQUEUE_OK;
}
int KernelEqueuePrivate::DeleteEvent(uintptr_t ident, int16_t filter) {
std::unique_lock lock(m_mutex);
if (m_closed) {
@ -273,10 +340,20 @@ int KernelEqueuePrivate::DeleteEvent(uintptr_t ident, int16_t filter) {
void KernelEqueuePrivate::RemoveDescriptorEvents(uintptr_t ident) {
std::unique_lock lock(m_mutex);
const auto removed = std::erase_if(m_events, [ident](const auto& e) {
return e.event.ident == ident && (e.event.filter == EVFILT_READ || e.event.filter == EVFILT_WRITE);
});
if (removed != 0) {
bool removed = false;
for (auto it = m_events.begin(); it != m_events.end();) {
if (it->event.ident != ident || (it->event.filter != EVFILT_READ && it->event.filter != EVFILT_WRITE)) {
++it;
continue;
}
if (it->filter.deleteEventFunc != nullptr) {
auto owner = it->filter.owner;
it->filter.deleteEventFunc(m_handle, &*it);
}
it = m_events.erase(it);
removed = true;
}
if (removed) {
WakePollers();
}
}
@ -315,16 +392,6 @@ int APS5_VABI EqueueDeleteEvent_nid_postfix(KernelEqueue eq, uintptr_t ident, in
}
int APS5_VABI kqueue_nid_postfix() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI kevent_nid_postfix() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name) {
if (eq == nullptr || name == nullptr) {
return SCE_KERNEL_ERROR_EINVAL;
@ -535,25 +602,7 @@ static int AddDescriptorEvent(KernelEqueue eq, int fd, std::size_t size, void* u
if (!GuestSockets::IsOpen(fd)) {
return SCE_KERNEL_ERROR_EBADF;
}
KernelEqueueEvent event{};
event.event.ident = static_cast<uintptr_t>(fd);
event.event.filter = filter;
event.event.flags = EV_ADD;
event.event.udata = udata;
event.filter.pollFunc = [](KernelEqueueEvent* e) {
std::int64_t data = 0;
bool eof = false;
if (!GuestSockets::Ready(static_cast<int>(e->event.ident), e->event.filter == EVFILT_WRITE, &data, &eof)) {
return false;
}
e->event.data = static_cast<intptr_t>(data);
e->event.flags = static_cast<uint16_t>(eof ? (e->event.flags | EV_EOF) : (e->event.flags & ~EV_EOF));
return true;
};
event.filter.resetFunc = [](KernelEqueueEvent* e) {
e->triggered = false;
};
return EqueueAddEvent_nid_postfix(eq, event);
return EqueueAddEvent_nid_postfix(eq, EqueueDescriptorEvent(fd, filter, EV_ADD, udata));
}
int APS5_VABI sceKernelAddReadEvent(KernelEqueue eq, int fd, std::size_t size, void* udata) {
@ -574,7 +623,30 @@ int APS5_VABI sceKernelDeleteWriteEvent(KernelEqueue eq, int fd) {
}
KernelEqueueEvent EqueueDescriptorEvent(int descriptor, int16_t filter, uint16_t flags, void* udata) {
KernelEqueueEvent event{};
event.event.ident = static_cast<uintptr_t>(descriptor);
event.event.filter = filter;
event.event.flags = flags;
event.event.udata = udata;
event.filter.pollFunc = [](KernelEqueueEvent* e) {
std::int64_t data = 0;
bool eof = false;
if (!GuestSockets::Ready(static_cast<int>(e->event.ident), e->event.filter == EVFILT_WRITE, &data, &eof)) {
return false;
}
e->event.data = static_cast<intptr_t>(data);
e->event.flags = static_cast<uint16_t>(eof ? (e->event.flags | EV_EOF) : (e->event.flags & ~EV_EOF));
return true;
};
event.filter.resetFunc = [](KernelEqueueEvent* e) {
e->triggered = false;
};
return event;
}
void EqueueDescriptorClosed(int descriptor) {
KqueueDescriptorClosed(descriptor);
std::vector<KernelEqueueRef> owners;
{
std::unique_lock lock(g_equeueMutex);

View File

@ -3,6 +3,7 @@
#include <cstdint>
#include <deque>
#include <functional>
#include <list>
#include <memory>
#include <mutex>
@ -24,8 +25,15 @@ static constexpr int16_t EVFILT_HRTIMER = -15;
static constexpr int16_t EVFILT_AMPR = -25;
static constexpr uint16_t EV_ADD = 0x0001;
static constexpr uint16_t EV_DELETE = 0x0002;
static constexpr uint16_t EV_ENABLE = 0x0004;
static constexpr uint16_t EV_DISABLE = 0x0008;
static constexpr uint16_t EV_ONESHOT = 0x0010;
static constexpr uint16_t EV_CLEAR = 0x0020;
static constexpr uint16_t EV_RECEIPT = 0x0040;
static constexpr uint16_t EV_DISPATCH = 0x0080;
static constexpr uint16_t EV_FORCEONESHOT = 0x0100;
static constexpr uint16_t EV_SYSFLAGS = 0xF000;
static constexpr uint16_t EV_ERROR = 0x4000;
static constexpr uint16_t EV_EOF = 0x8000;
@ -58,7 +66,7 @@ struct KernelEqueueEvent {
class KernelEqueuePrivate {
public:
explicit KernelEqueuePrivate(KernelEqueue handle);
explicit KernelEqueuePrivate(KernelEqueue handle, bool reportOncePerWait = false);
~KernelEqueuePrivate();
KernelEqueuePrivate(const KernelEqueuePrivate&) = delete;
@ -70,16 +78,20 @@ public:
int AddEvent(const KernelEqueueEvent& event);
int TriggerEvent(uintptr_t ident, int16_t filter, void* triggerData);
int DeleteEvent(uintptr_t ident, int16_t filter);
bool HasEvent(uintptr_t ident, int16_t filter);
int EditEvent(uintptr_t ident, int16_t filter, const std::function<void(KernelEqueueEvent&)>& edit);
int GetTriggeredEvents(KernelEvent* ev, int num);
int WaitForEvents(KernelEvent* ev, int num, uint32_t micros);
int WaitForEventsUntil(KernelEvent* ev, int num, std::uint64_t deadlineNanos);
void RemoveDescriptorEvents(uintptr_t ident);
void Close();
static uint64_t MonotonicNs();
private:
void TriggerExpiredTimers(uint64_t nowNs);
int CollectTriggered(KernelEvent* ev, int num);
bool NextTimerWaitMicros(uint64_t nowNs, uint32_t* out) const;
int CollectTriggered(KernelEvent* ev, int num);
int CollectOncePerWait(KernelEvent* ev, int num);
bool PollEvents();
void WakePollers();
void WaitForDescriptors(std::unique_lock<std::mutex>& lock, std::uint64_t deadlineNanos);
@ -90,11 +102,15 @@ private:
TimedWait::Condition m_cond;
std::string m_name;
KernelEqueue m_handle;
bool m_reportOncePerWait;
bool m_closed = false;
};
using KernelEqueueRef = std::shared_ptr<KernelEqueuePrivate>;
void EqueueDescriptorClosed(int descriptor);
void KqueueDescriptorClosed(int descriptor);
bool IsKqueueDescriptor(int descriptor);
KernelEqueueEvent EqueueDescriptorEvent(int descriptor, int16_t filter, uint16_t flags, void* udata);
extern "C" {
KernelEqueueRef EqueuePin_nid_postfix(KernelEqueue eq);

View File

@ -0,0 +1,391 @@
#include "prx/libkernel/Equeue/Equeue.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/File/include/File.hpp"
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
#include "prx/libkernel/Time/include/TimedWait.hpp"
#include <algorithm>
#include <cerrno>
#include <climits>
#include <cstdint>
#include <limits>
#include <mutex>
#include <optional>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#else
#include <fcntl.h>
#include <unistd.h>
#endif
extern "C" int* APS5_VABI __error_nid_postfix();
namespace {
constexpr int GUEST_ENOENT = 2;
constexpr int GUEST_EBADF = 9;
constexpr int GUEST_EFAULT = 14;
constexpr int GUEST_EINVAL = 22;
constexpr int GUEST_ENFILE = 23;
constexpr int GUEST_EMFILE = 24;
constexpr int KQ_NEVENTS = 8;
constexpr std::uint32_t NOTE_FFCTRLMASK = 0xc0000000;
constexpr std::uint32_t NOTE_FFLAGSMASK = 0x00ffffff;
constexpr std::uint32_t NOTE_FFAND = 0x40000000;
constexpr std::uint32_t NOTE_FFOR = 0x80000000;
constexpr std::uint32_t NOTE_FFCOPY = 0xc0000000;
constexpr std::uint32_t NOTE_TRIGGER = 0x01000000;
constexpr std::uint32_t NOTE_LOWAT = 0x0001;
constexpr std::uint32_t NOTE_SECONDS = 0x0001;
constexpr std::uint32_t NOTE_MSECONDS = 0x0002;
constexpr std::uint32_t NOTE_USECONDS = 0x0004;
constexpr std::uint32_t NOTE_NSECONDS = 0x0008;
constexpr std::uint32_t NOTE_TIMER_PRECMASK = NOTE_SECONDS | NOTE_MSECONDS | NOTE_USECONDS | NOTE_NSECONDS;
constexpr std::uint16_t KNOTE_CLEARED_FLAGS = EV_ADD | EV_DELETE | EV_ENABLE | EV_DISABLE | EV_FORCEONESHOT;
std::mutex g_kqueueMutex;
std::unordered_map<int, KernelEqueueRef> g_kqueues;
int PosixFailure(int error) {
*__error_nid_postfix() = error;
return -1;
}
int GuestErrno(int sceError) {
return sceError & 0xffff;
}
KernelEqueueRef FindKqueue(int descriptor) {
std::lock_guard lock(g_kqueueMutex);
const auto found = g_kqueues.find(descriptor);
return found != g_kqueues.end() ? found->second : KernelEqueueRef{};
}
int ReserveDescriptor() {
#ifdef _WIN32
return ::_open("NUL", _O_RDONLY | _O_BINARY);
#else
return ::open("/dev/null", O_RDONLY | O_CLOEXEC);
#endif
}
void ReleaseDescriptor(int descriptor) {
#ifdef _WIN32
::_close(descriptor);
#else
::close(descriptor);
#endif
}
std::optional<std::uint64_t> TimerPeriodNanos(std::int64_t data, std::uint32_t fflags) {
std::uint64_t unit = 0;
switch (fflags & NOTE_TIMER_PRECMASK) {
case NOTE_SECONDS:
unit = 1000000000ULL;
break;
case NOTE_MSECONDS:
case 0:
unit = 1000000ULL;
break;
case NOTE_USECONDS:
unit = 1000ULL;
break;
case NOTE_NSECONDS:
if (data >= 1000000000LL && data % 1000000000LL != 0) {
NotImplemented_nid_no_patch("kevent: EVFILT_TIMER NOTE_NSECONDS period above one second with a fraction");
}
unit = 1;
break;
default:
return std::nullopt;
}
const auto value = static_cast<std::uint64_t>(data);
if (value > std::numeric_limits<std::uint64_t>::max() / unit) {
return std::numeric_limits<std::uint64_t>::max();
}
return value * unit;
}
int DescriptorFilterError(int kqueueDescriptor, std::uintptr_t ident) {
if (ident > static_cast<std::uintptr_t>(INT_MAX)) {
return GUEST_EBADF;
}
const int descriptor = static_cast<int>(ident);
if (descriptor >= GuestSockets::FirstDescriptor) {
return GuestSockets::IsOpen(descriptor) ? 0 : GUEST_EBADF;
}
if (!DescriptorIsOpen_nid_no_patch(descriptor)) {
return GUEST_EBADF;
}
return descriptor == kqueueDescriptor ? GUEST_EINVAL : 0;
}
int AddDescriptorKnote(const KernelEqueueRef& queue, KernelEvent& change, std::uint16_t flags) {
const int descriptor = static_cast<int>(change.ident);
if (IsKqueueDescriptor(descriptor)) {
if (change.filter != EVFILT_READ) {
return GUEST_EINVAL;
}
NotImplemented_nid_no_patch("kevent: EVFILT_READ on a kqueue descriptor");
}
if (descriptor < GuestSockets::FirstDescriptor) {
NotImplemented_nid_no_patch(change.filter == EVFILT_READ ? "kevent: EVFILT_READ on a non-socket descriptor" : "kevent: EVFILT_WRITE on a non-socket descriptor");
}
if ((change.fflags & NOTE_LOWAT) != 0) {
NotImplemented_nid_no_patch("kevent: NOTE_LOWAT");
}
if ((flags & EV_CLEAR) != 0) {
NotImplemented_nid_no_patch("kevent: EV_CLEAR on a socket");
}
auto event = EqueueDescriptorEvent(descriptor, change.filter, flags, change.udata);
change.fflags = 0;
change.data = 0;
return GuestErrno(queue->AddEvent(event));
}
int AddTimerKnote(const KernelEqueueRef& queue, KernelEvent& change, std::uint16_t flags) {
std::int64_t period = change.data;
if (period < 0) {
return GUEST_EINVAL;
}
if (period == 0 && (flags & EV_ONESHOT) == 0) {
period = 1;
}
if ((change.fflags & ~NOTE_TIMER_PRECMASK) != 0) {
return GUEST_EINVAL;
}
const auto periodNs = TimerPeriodNanos(period, change.fflags);
if (!periodNs) {
return GUEST_EINVAL;
}
const std::uint64_t now = KernelEqueuePrivate::MonotonicNs();
KernelEqueueEvent event{};
event.deadlineNs = *periodNs <= std::numeric_limits<std::uint64_t>::max() - now ? now + *periodNs : std::numeric_limits<std::uint64_t>::max();
event.intervalNs = (flags & EV_ONESHOT) != 0 ? 0 : *periodNs;
event.event = change;
event.event.flags = static_cast<std::uint16_t>(flags | EV_CLEAR);
event.event.fflags = 0;
event.event.data = 0;
change.fflags = 0;
change.data = 0;
return GuestErrno(queue->AddEvent(event));
}
int AddUserKnote(const KernelEqueueRef& queue, KernelEvent& change, std::uint16_t flags) {
KernelEqueueEvent event{};
event.event = change;
event.event.flags = flags;
event.filter.resetFunc = [](KernelEqueueEvent* e) {
if ((e->event.flags & EV_CLEAR) != 0) {
e->triggered = false;
}
};
change.fflags = 0;
change.data = 0;
return GuestErrno(queue->AddEvent(event));
}
void TouchUserKnote(KernelEqueueEvent& event, KernelEvent& change) {
if ((change.fflags & NOTE_TRIGGER) != 0) {
event.triggered = true;
}
const std::uint32_t control = change.fflags & NOTE_FFCTRLMASK;
change.fflags &= NOTE_FFLAGSMASK;
if (control == NOTE_FFAND) {
event.event.fflags &= change.fflags;
} else if (control == NOTE_FFOR) {
event.event.fflags |= change.fflags;
} else if (control == NOTE_FFCOPY) {
event.event.fflags = change.fflags;
}
event.event.data = change.data;
if ((change.flags & EV_CLEAR) != 0) {
event.triggered = false;
}
}
int ModifyKnote(const KernelEqueueRef& queue, KernelEvent& change) {
if (change.filter == EVFILT_TIMER) {
NotImplemented_nid_no_patch("kevent: changing an existing EVFILT_TIMER");
}
if ((change.filter == EVFILT_READ || change.filter == EVFILT_WRITE) && (change.fflags & NOTE_LOWAT) != 0) {
NotImplemented_nid_no_patch("kevent: NOTE_LOWAT");
}
return GuestErrno(queue->EditEvent(change.ident, change.filter, [&change](KernelEqueueEvent& event) {
event.event.udata = change.udata;
if (change.filter == EVFILT_USER) {
TouchUserKnote(event, change);
}
}));
}
int Register(const KernelEqueueRef& queue, int kqueueDescriptor, KernelEvent& change) {
if ((change.flags & (EV_ENABLE | EV_DISABLE)) == (EV_ENABLE | EV_DISABLE)) {
return GUEST_EINVAL;
}
if (change.filter > 0) {
return GUEST_EINVAL;
}
if (change.filter != EVFILT_READ && change.filter != EVFILT_WRITE && change.filter != EVFILT_TIMER && change.filter != EVFILT_USER) {
NotImplemented_nid_no_patch(("kevent: filter " + std::to_string(change.filter)).c_str());
}
if ((change.flags & EV_DISABLE) != 0) {
NotImplemented_nid_no_patch("kevent: EV_DISABLE");
}
if ((change.flags & EV_DISPATCH) != 0) {
NotImplemented_nid_no_patch("kevent: EV_DISPATCH");
}
if ((change.flags & EV_FORCEONESHOT) != 0) {
NotImplemented_nid_no_patch("kevent: EV_FORCEONESHOT");
}
if (change.filter == EVFILT_READ || change.filter == EVFILT_WRITE) {
if (const int error = DescriptorFilterError(kqueueDescriptor, change.ident)) {
return error;
}
}
if (!queue->HasEvent(change.ident, change.filter)) {
if ((change.flags & EV_ADD) == 0) {
return GUEST_ENOENT;
}
const auto flags = static_cast<std::uint16_t>(change.flags & ~KNOTE_CLEARED_FLAGS);
if (change.filter == EVFILT_TIMER) {
return AddTimerKnote(queue, change, flags);
}
if (change.filter == EVFILT_USER) {
return AddUserKnote(queue, change, flags);
}
return AddDescriptorKnote(queue, change, flags);
}
if ((change.flags & EV_DELETE) != 0) {
return GuestErrno(queue->DeleteEvent(change.ident, change.filter));
}
return ModifyKnote(queue, change);
}
std::optional<std::uint64_t> ScanDeadline(const KernelTimespec& timeout) {
if (timeout.tv_sec > INT32_MAX) {
return std::nullopt;
}
const std::uint64_t span = static_cast<std::uint64_t>(timeout.tv_sec) * 1000000000ULL + static_cast<std::uint64_t>(timeout.tv_nsec);
return TimedWait::NowNanos() + span;
}
}
bool IsKqueueDescriptor(int descriptor) {
return static_cast<bool>(FindKqueue(descriptor));
}
void KqueueDescriptorClosed(int descriptor) {
KernelEqueueRef closed;
std::vector<KernelEqueueRef> queues;
{
std::lock_guard lock(g_kqueueMutex);
const auto found = g_kqueues.find(descriptor);
if (found != g_kqueues.end()) {
closed = std::move(found->second);
g_kqueues.erase(found);
}
for (const auto& [kqueueDescriptor, queue] : g_kqueues) {
queues.push_back(queue);
}
}
if (closed) {
closed->Close();
}
for (const auto& queue : queues) {
queue->RemoveDescriptorEvents(static_cast<std::uintptr_t>(descriptor));
}
}
extern "C" {
int APS5_VABI kqueue_nid_postfix() {
const int descriptor = ReserveDescriptor();
if (descriptor < 0) {
if (errno == EMFILE) return PosixFailure(GUEST_EMFILE);
if (errno == ENFILE) return PosixFailure(GUEST_ENFILE);
throw std::runtime_error(std::string(__func__) + ": reserving a descriptor failed, errno=" + std::to_string(errno));
}
if (descriptor >= GuestSockets::FirstDescriptor) {
ReleaseDescriptor(descriptor);
throw std::runtime_error(std::string(__func__) + ": host descriptor reached the guest socket range");
}
auto queue = std::make_shared<KernelEqueuePrivate>(static_cast<KernelEqueue>(descriptor), true);
queue->SetName("kqueue");
std::lock_guard lock(g_kqueueMutex);
g_kqueues[descriptor] = std::move(queue);
return descriptor;
}
int APS5_VABI kevent_nid_postfix(int descriptor, const KernelEvent* changelist, int nchanges, KernelEvent* eventlist, int nevents, const KernelTimespec* timeout) {
const auto queue = FindKqueue(descriptor);
if (!queue) {
return PosixFailure(GUEST_EBADF);
}
if (nevents < 0) {
NotImplemented_nid_no_patch("kevent: negative nevents");
}
if (nchanges > 0 && changelist == nullptr) {
return PosixFailure(GUEST_EFAULT);
}
int errors = 0;
for (int i = 0; i < nchanges; ++i) {
KernelEvent change = changelist[i];
if (change.filter == 0) {
continue;
}
change.flags = static_cast<std::uint16_t>(change.flags & ~EV_SYSFLAGS);
const int error = Register(queue, descriptor, change);
if (error == 0 && (change.flags & EV_RECEIPT) == 0) {
continue;
}
if (nevents == 0) {
return error == 0 ? 0 : PosixFailure(error);
}
change.flags = EV_ERROR;
change.data = error;
if (eventlist != nullptr) {
*eventlist++ = change;
}
--nevents;
++errors;
}
if (errors != 0) {
return errors;
}
if (nevents == 0) {
return 0;
}
std::optional<std::uint64_t> deadline;
bool poll = false;
if (timeout != nullptr) {
if (timeout->tv_sec < 0 || timeout->tv_nsec < 0 || timeout->tv_nsec >= 1000000000LL) {
return PosixFailure(GUEST_EINVAL);
}
poll = timeout->tv_sec == 0 && timeout->tv_nsec == 0;
if (!poll) {
deadline = ScanDeadline(*timeout);
}
}
KernelEvent scratch[KQ_NEVENTS];
KernelEvent* target = eventlist != nullptr ? eventlist : scratch;
const int capacity = eventlist != nullptr ? nevents : std::min(nevents, KQ_NEVENTS);
const int count = poll ? queue->GetTriggeredEvents(target, capacity) : queue->WaitForEventsUntil(target, capacity, deadline.value_or(0));
if (count < 0) {
return PosixFailure(GuestErrno(count));
}
if (count > 0 && eventlist == nullptr) {
return PosixFailure(GUEST_EFAULT);
}
return count;
}
}

View File

@ -12,6 +12,7 @@
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
#include "prx/libkernel/File/include/RandomDevice.hpp"
#include "prx/libkernel/KernelErrors.hpp"
#include "prx/libkernel/Equeue/Equeue.hpp"
#include "SceTypes.hpp"
#include <cerrno>
@ -221,6 +222,7 @@ int OpenInDirectory_nid_no_patch(int directory, const char* path, int flags, std
int APS5_VABI sceKernelClose(int d) {
if (d >= GuestSockets::FirstDescriptor) return GuestSockets::Close(d) == 0 ? 0 : SceKernelError(*__error_nid_postfix());
EqueueDescriptorClosed(d);
File::ForgetRandomDevice(d);
File::ForgetDescriptor(d);
ForgetJitSharedMemory(d);

View File

@ -17,6 +17,7 @@
#include "prx/libkernel/File/include/FileLock.hpp"
#include "prx/libkernel/File/include/RandomDevice.hpp"
#include "prx/libkernel/KernelErrors.hpp"
#include "prx/libkernel/Equeue/Equeue.hpp"
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
#include "prx/libkernel/Socket/include/SocketPoll.hpp"
#include <atomic>
@ -496,6 +497,7 @@ int APS5_VABI close_nid_postfix(int d) {
*__error_nid_postfix() = socketError;
return -1;
}
EqueueDescriptorClosed(d);
File::ForgetRandomDevice(d);
File::ForgetDescriptor(d);
ForgetJitSharedMemory(d);
@ -545,6 +547,7 @@ extern "C" {
int APS5_VABI dup_nid_postfix(int d) {
if (d >= GuestSockets::FirstDescriptor) return GuestSockets::Duplicate(d);
if (d < 0) return PosixFailure(GUEST_EBADF);
if (IsKqueueDescriptor(d)) throw std::runtime_error(std::string(__func__) + ": duplicating a kqueue descriptor is not supported");
#ifdef _WIN32
const int duplicate = WithoutParameterHandler([d] { return ::_dup(d); });
if (duplicate >= 0) File::DuplicateDirectoryDescriptor(d, duplicate);
@ -572,11 +575,13 @@ int APS5_VABI dup2_nid_postfix(int from, int to) {
if (socketTo)
throw std::runtime_error(std::string(__func__) + ": moving host descriptor " + std::to_string(from) + " into the guest socket range is not supported");
const bool sourceIsRandom = File::IsRandomDevice(from);
if (from != to && IsKqueueDescriptor(from)) throw std::runtime_error(std::string(__func__) + ": duplicating a kqueue descriptor is not supported");
#ifdef _WIN32
if (WithoutParameterHandler([from] { return ::_get_osfhandle(from) == -1 ? -1 : 0; }) != 0) return PosixFailure(GUEST_EBADF);
if (from == to) return to;
if (to == 2) PreserveHostStandardError();
if (WithoutParameterHandler([from, to] { return ::_dup2(from, to); }) != 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff);
EqueueDescriptorClosed(to);
File::ForgetDirectoryDescriptor(to);
File::DuplicateDirectoryDescriptor(from, to);
File::ForgetFileLock(to);
@ -593,6 +598,7 @@ int APS5_VABI dup2_nid_postfix(int from, int to) {
if (to == 2) PreserveHostStandardError();
const int result = ::dup2(from, to);
if (result < 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff);
EqueueDescriptorClosed(to);
File::ForgetRandomDevice(to);
if (sourceIsRandom) File::RememberRandomDevice(to);
File::ShareDescriptor(from, to);

View File

@ -0,0 +1,241 @@
#include "SceTypes.hpp"
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <stdexcept>
#include <thread>
extern "C" {
int APS5_VABI kqueue_nid_postfix();
int APS5_VABI kevent_nid_postfix(int, const KernelEvent*, int, KernelEvent*, int, const KernelTimespec*);
int APS5_VABI close_nid_postfix(int);
int APS5_VABI dup_nid_postfix(int);
int APS5_VABI pipe_nid_postfix(int*);
int APS5_VABI socketpair_nid_postfix(int, int, int, int*);
std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int);
std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int);
int* APS5_VABI __error_nid_postfix();
}
static constexpr int GuestEnoent = 2;
static constexpr int GuestEbadf = 9;
static constexpr int GuestEinval = 22;
static constexpr int Unix = 1;
static constexpr int Stream = 1;
static constexpr int FirstSocket = 0x10000000;
static constexpr std::int16_t EvfiltRead = -1;
static constexpr std::int16_t EvfiltWrite = -2;
static constexpr std::int16_t EvfiltProc = -5;
static constexpr std::int16_t EvfiltTimer = -7;
static constexpr std::int16_t EvfiltUser = -11;
static constexpr std::uint16_t EvAdd = 0x0001;
static constexpr std::uint16_t EvDelete = 0x0002;
static constexpr std::uint16_t EvEnable = 0x0004;
static constexpr std::uint16_t EvDisable = 0x0008;
static constexpr std::uint16_t EvOneshot = 0x0010;
static constexpr std::uint16_t EvClear = 0x0020;
static constexpr std::uint16_t EvReceipt = 0x0040;
static constexpr std::uint16_t EvError = 0x4000;
static constexpr std::uint32_t NoteTrigger = 0x01000000;
static constexpr std::uint32_t NoteFfcopy = 0xc0000000;
static constexpr std::uint32_t NoteFfor = 0x80000000;
static constexpr std::uint32_t NoteUseconds = 0x0004;
static void Require(bool value) { if (!value) std::abort(); }
static KernelEvent Change(std::uintptr_t ident, std::int16_t filter, std::uint16_t flags, std::uint32_t fflags = 0, std::intptr_t data = 0, void* udata = nullptr) {
KernelEvent change{};
change.ident = ident;
change.filter = filter;
change.flags = flags;
change.fflags = fflags;
change.data = data;
change.udata = udata;
return change;
}
static int Apply(int kq, const KernelEvent& change) {
return kevent_nid_postfix(kq, &change, 1, nullptr, 0, nullptr);
}
static void RequireFailure(int result, int error) {
Require(result == -1);
Require(*__error_nid_postfix() == error);
}
static int Poll(int kq, KernelEvent* events, int count) {
const KernelTimespec zero{0, 0};
return kevent_nid_postfix(kq, nullptr, 0, events, count, &zero);
}
static int Wait(int kq, KernelEvent* events, int count) {
const KernelTimespec second{1, 0};
return kevent_nid_postfix(kq, nullptr, 0, events, count, &second);
}
template <typename TCall> static bool Throws(TCall call) {
try {
call();
} catch (const std::runtime_error&) {
return true;
}
return false;
}
static void CreateAndClose() {
const int kq = kqueue_nid_postfix();
Require(kq >= 0 && kq < FirstSocket);
const int other = kqueue_nid_postfix();
Require(other >= 0 && other != kq);
KernelEvent event{};
Require(Poll(kq, &event, 1) == 0);
Require(Throws([kq] { dup_nid_postfix(kq); }));
Require(close_nid_postfix(kq) == 0);
RequireFailure(Poll(kq, &event, 1), GuestEbadf);
Require(close_nid_postfix(other) == 0);
int pipe[2];
Require(pipe_nid_postfix(pipe) == 0);
RequireFailure(Poll(pipe[0], &event, 1), GuestEbadf);
Require(close_nid_postfix(pipe[0]) == 0 && close_nid_postfix(pipe[1]) == 0);
}
static void Timers() {
const int kq = kqueue_nid_postfix();
int marker = 0;
KernelEvent events[4]{};
Require(Apply(kq, Change(1, EvfiltTimer, EvAdd, 0, 10, &marker)) == 0);
Require(Poll(kq, events, 4) == 0);
Require(Wait(kq, events, 4) == 1);
Require(events[0].ident == 1 && events[0].filter == EvfiltTimer && events[0].data >= 1 && events[0].udata == &marker);
Require((events[0].flags & EvClear) != 0);
Require(Wait(kq, events, 4) == 1 && events[0].ident == 1);
Require(Apply(kq, Change(1, EvfiltTimer, EvDelete)) == 0);
RequireFailure(Apply(kq, Change(1, EvfiltTimer, EvDelete)), GuestEnoent);
Require(Apply(kq, Change(2, EvfiltTimer, EvAdd | EvOneshot, NoteUseconds, 0)) == 0);
Require(Wait(kq, events, 4) == 1 && events[0].ident == 2 && events[0].data == 1);
Require(Poll(kq, events, 4) == 0);
RequireFailure(Apply(kq, Change(2, EvfiltTimer, EvDelete)), GuestEnoent);
RequireFailure(Apply(kq, Change(3, EvfiltTimer, EvAdd, 0, -1)), GuestEinval);
RequireFailure(Apply(kq, Change(3, EvfiltTimer, EvAdd, 0x10, 5)), GuestEinval);
RequireFailure(Apply(kq, Change(3, EvfiltTimer, EvAdd, NoteUseconds | 0x1, 5)), GuestEinval);
Require(Apply(kq, Change(4, EvfiltTimer, EvAdd, 0, 100000)) == 0);
Require(Throws([kq] { Apply(kq, Change(4, EvfiltTimer, EvAdd, 0, 5)); }));
Require(close_nid_postfix(kq) == 0);
}
static void UserEvents() {
const int kq = kqueue_nid_postfix();
int marker = 0;
KernelEvent events[4]{};
Require(Apply(kq, Change(5, EvfiltUser, EvAdd | EvClear, 0, 0, &marker)) == 0);
Require(Poll(kq, events, 4) == 0);
std::thread trigger([kq, &marker] {
std::this_thread::sleep_for(std::chrono::milliseconds(20));
Require(Apply(kq, Change(5, EvfiltUser, 0, NoteTrigger | NoteFfcopy | 0x123, 77, &marker)) == 0);
});
Require(kevent_nid_postfix(kq, nullptr, 0, events, 4, nullptr) == 1);
trigger.join();
Require(events[0].ident == 5 && events[0].filter == EvfiltUser && events[0].fflags == 0x123 && events[0].data == 77);
Require(events[0].udata == &marker && events[0].flags == EvClear);
Require(Poll(kq, events, 4) == 0);
Require(Apply(kq, Change(5, EvfiltUser, 0, NoteTrigger | NoteFfor | 0x400, 78)) == 0);
Require(Poll(kq, events, 4) == 1 && events[0].fflags == 0x523 && events[0].data == 78);
Require(Apply(kq, Change(6, EvfiltUser, EvAdd)) == 0);
Require(Apply(kq, Change(6, EvfiltUser, 0, NoteTrigger)) == 0);
for (int i = 0; i < 2; ++i) {
Require(Poll(kq, events, 4) == 1 && events[0].ident == 6);
}
Require(Apply(kq, Change(6, EvfiltUser, EvClear)) == 0);
Require(Poll(kq, events, 4) == 0);
Require(Apply(kq, Change(6, EvfiltUser, EvDelete)) == 0);
KernelEvent receipt[2]{};
const KernelEvent change = Change(7, EvfiltUser, EvAdd | EvReceipt, 0x55, 9, &marker);
Require(kevent_nid_postfix(kq, &change, 1, receipt, 2, nullptr) == 1);
Require(receipt[0].ident == 7 && receipt[0].flags == EvError && receipt[0].data == 0 && receipt[0].fflags == 0);
Require(Apply(kq, Change(7, EvfiltUser, 0, NoteTrigger)) == 0);
Require(Poll(kq, events, 4) == 1 && events[0].fflags == 0x55 && events[0].data == 0 && events[0].flags == EvReceipt);
Require(close_nid_postfix(kq) == 0);
}
static void SocketPair() {
const int kq = kqueue_nid_postfix();
int pair[2];
Require(socketpair_nid_postfix(Unix, Stream, 0, pair) == 0);
int marker = 0;
KernelEvent events[4]{};
Require(Apply(kq, Change(static_cast<std::uintptr_t>(pair[0]), EvfiltRead, EvAdd, 0, 0, &marker)) == 0);
Require(Poll(kq, events, 4) == 0);
Require(send_nid_postfix(pair[1], "abc", 3, 0) == 3);
Require(Wait(kq, events, 4) == 1);
Require(events[0].ident == static_cast<std::uintptr_t>(pair[0]) && events[0].filter == EvfiltRead && events[0].data == 3 && events[0].udata == &marker);
Require(Poll(kq, events, 4) == 1);
char buffer[4]{};
Require(recv_nid_postfix(pair[0], buffer, sizeof(buffer), 0) == 3);
Require(Poll(kq, events, 4) == 0);
Require(Apply(kq, Change(static_cast<std::uintptr_t>(pair[1]), EvfiltWrite, EvAdd | EvOneshot)) == 0);
Require(Wait(kq, events, 4) == 1 && events[0].filter == EvfiltWrite);
RequireFailure(Apply(kq, Change(static_cast<std::uintptr_t>(pair[1]), EvfiltWrite, EvDelete)), GuestEnoent);
Require(Throws([kq, &pair] { Apply(kq, Change(static_cast<std::uintptr_t>(pair[1]), EvfiltRead, EvAdd | EvClear)); }));
Require(close_nid_postfix(pair[0]) == 0);
RequireFailure(Apply(kq, Change(static_cast<std::uintptr_t>(pair[0]), EvfiltRead, EvDelete)), GuestEbadf);
Require(close_nid_postfix(pair[1]) == 0);
Require(close_nid_postfix(kq) == 0);
}
static void Errors() {
const int kq = kqueue_nid_postfix();
KernelEvent events[4]{};
const KernelEvent changes[3] = {
Change(FirstSocket + 4000, EvfiltRead, EvAdd),
Change(8, EvfiltUser, EvDelete),
Change(9, EvfiltUser, EvAdd | EvEnable | EvDisable),
};
Require(kevent_nid_postfix(kq, changes, 3, events, 4, nullptr) == 3);
Require(events[0].flags == EvError && events[0].data == GuestEbadf && events[0].ident == FirstSocket + 4000);
Require(events[1].flags == EvError && events[1].data == GuestEnoent);
Require(events[2].flags == EvError && events[2].data == GuestEinval);
RequireFailure(kevent_nid_postfix(kq, changes, 3, events, 1, nullptr), GuestEnoent);
Require(events[0].flags == EvError && events[0].data == GuestEbadf);
RequireFailure(kevent_nid_postfix(kq, changes, 3, nullptr, 0, nullptr), GuestEbadf);
RequireFailure(Apply(kq, Change(static_cast<std::uintptr_t>(kq), EvfiltRead, EvAdd)), GuestEinval);
RequireFailure(Apply(kq, Change(1, 1, EvAdd)), GuestEinval);
Require(Throws([kq] { Apply(kq, Change(1, EvfiltProc, EvAdd)); }));
Require(Throws([kq] { Apply(kq, Change(1, EvfiltUser, EvAdd | EvDisable)); }));
int pipe[2];
Require(pipe_nid_postfix(pipe) == 0);
Require(Throws([kq, &pipe] { Apply(kq, Change(static_cast<std::uintptr_t>(pipe[0]), EvfiltRead, EvAdd)); }));
Require(close_nid_postfix(pipe[0]) == 0 && close_nid_postfix(pipe[1]) == 0);
const KernelEvent skipped = Change(1, 0, EvAdd);
const KernelTimespec zero{0, 0};
Require(kevent_nid_postfix(kq, &skipped, 1, events, 4, &zero) == 0);
const KernelTimespec invalid{0, 1000000000};
RequireFailure(kevent_nid_postfix(kq, nullptr, 0, events, 4, &invalid), GuestEinval);
const KernelTimespec negative{-1, 0};
RequireFailure(kevent_nid_postfix(kq, nullptr, 0, events, 4, &negative), GuestEinval);
Require(close_nid_postfix(kq) == 0);
}
static void Timeout() {
const int kq = kqueue_nid_postfix();
KernelEvent event{};
const KernelTimespec timeout{0, 30000000};
const auto start = std::chrono::steady_clock::now();
Require(kevent_nid_postfix(kq, nullptr, 0, &event, 1, &timeout) == 0);
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(29));
Require(kevent_nid_postfix(kq, nullptr, 0, &event, 0, nullptr) == 0);
Require(close_nid_postfix(kq) == 0);
}
int main() {
CreateAndClose();
Timers();
UserEvents();
SocketPair();
Errors();
Timeout();
return 0;
}

View File

@ -8,8 +8,6 @@ extern "C" {
int APS5_VABI sceNetShowIfconfig();
int APS5_VABI scePadUnknown_n3kSX62fgNo();
int APS5_VABI _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_();
int APS5_VABI kqueue_nid_postfix();
int APS5_VABI kevent_nid_postfix();
}
namespace {
@ -23,8 +21,6 @@ const Export exports[] = {
{"sceNetShowIfconfig", sceNetShowIfconfig},
{"scePadUnknown_n3kSX62fgNo", scePadUnknown_n3kSX62fgNo},
{"_ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_", _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_},
{"kqueue_nid_postfix", kqueue_nid_postfix},
{"kevent_nid_postfix", kevent_nid_postfix},
};
bool ReportsNotImplemented(const Export& entry) {