diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index e0e710285..7974c0741 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -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:$") 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:$") + 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) diff --git a/core/libs/prx/libkernel/Equeue/Equeue.cpp b/core/libs/prx/libkernel/Equeue/Equeue.cpp index da6601e52..bd18cf69a 100644 --- a/core/libs/prx/libkernel/Equeue/Equeue.cpp +++ b/core/libs/prx/libkernel/Equeue/Equeue.cpp @@ -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& 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 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(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& 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(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(e->event.ident), e->event.filter == EVFILT_WRITE, &data, &eof)) { - return false; - } - e->event.data = static_cast(data); - e->event.flags = static_cast(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(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(e->event.ident), e->event.filter == EVFILT_WRITE, &data, &eof)) { + return false; + } + e->event.data = static_cast(data); + e->event.flags = static_cast(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 owners; { std::unique_lock lock(g_equeueMutex); diff --git a/core/libs/prx/libkernel/Equeue/Equeue.hpp b/core/libs/prx/libkernel/Equeue/Equeue.hpp index f5b291dbd..ad94cc01d 100644 --- a/core/libs/prx/libkernel/Equeue/Equeue.hpp +++ b/core/libs/prx/libkernel/Equeue/Equeue.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -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& 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& 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; 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); diff --git a/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp b/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp new file mode 100644 index 000000000..79a68f0e1 --- /dev/null +++ b/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#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 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 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(data); + if (value > std::numeric_limits::max() / unit) { + return std::numeric_limits::max(); + } + return value * unit; +} + +int DescriptorFilterError(int kqueueDescriptor, std::uintptr_t ident) { + if (ident > static_cast(INT_MAX)) { + return GUEST_EBADF; + } + const int descriptor = static_cast(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(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::max() - now ? now + *periodNs : std::numeric_limits::max(); + event.intervalNs = (flags & EV_ONESHOT) != 0 ? 0 : *periodNs; + event.event = change; + event.event.flags = static_cast(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(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 ScanDeadline(const KernelTimespec& timeout) { + if (timeout.tv_sec > INT32_MAX) { + return std::nullopt; + } + const std::uint64_t span = static_cast(timeout.tv_sec) * 1000000000ULL + static_cast(timeout.tv_nsec); + return TimedWait::NowNanos() + span; +} + +} + +bool IsKqueueDescriptor(int descriptor) { + return static_cast(FindKqueue(descriptor)); +} + +void KqueueDescriptorClosed(int descriptor) { + KernelEqueueRef closed; + std::vector 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(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(static_cast(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(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 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; +} + +} diff --git a/core/libs/prx/libkernel/File/src/Open.cpp b/core/libs/prx/libkernel/File/src/Open.cpp index 2aeae1ed4..2a1edd018 100644 --- a/core/libs/prx/libkernel/File/src/Open.cpp +++ b/core/libs/prx/libkernel/File/src/Open.cpp @@ -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 @@ -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); diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index 18027b03c..272922a2a 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -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 @@ -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); diff --git a/core/libs/tests/GuestKqueue.cpp b/core/libs/tests/GuestKqueue.cpp new file mode 100644 index 000000000..99b65145c --- /dev/null +++ b/core/libs/tests/GuestKqueue.cpp @@ -0,0 +1,241 @@ +#include "SceTypes.hpp" +#include +#include +#include +#include +#include + +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 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(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(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(pair[1]), EvfiltWrite, EvAdd | EvOneshot)) == 0); + Require(Wait(kq, events, 4) == 1 && events[0].filter == EvfiltWrite); + RequireFailure(Apply(kq, Change(static_cast(pair[1]), EvfiltWrite, EvDelete)), GuestEnoent); + Require(Throws([kq, &pair] { Apply(kq, Change(static_cast(pair[1]), EvfiltRead, EvAdd | EvClear)); })); + Require(close_nid_postfix(pair[0]) == 0); + RequireFailure(Apply(kq, Change(static_cast(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(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(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; +} diff --git a/core/libs/tests/GuestPPSA01530Exports.cpp b/core/libs/tests/GuestPPSA01530Exports.cpp index 23f05249e..aeafa78f9 100644 --- a/core/libs/tests/GuestPPSA01530Exports.cpp +++ b/core/libs/tests/GuestPPSA01530Exports.cpp @@ -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) {