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:
parent
27873b0f74
commit
9ef4aa2c40
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue