From e99816e8f3088fb890ea94de13cd2616692cac8a Mon Sep 17 00:00:00 2001 From: screamm Date: Mon, 12 Oct 2026 09:39:18 +0200 Subject: [PATCH 1/5] fix(libkernel): give guest sockets ordinary descriptor numbers below FD_SETSIZE libkernel numbered guest sockets from GuestSockets::FirstDescriptor (0x10000000), so a title that puts a socket in a 1024-bit fd_set writes fd / 8 bytes past it. The Epic Online Services SDK does this on its RtcNetworkThread and three titles stop there (#1794): Palworld PPSA23126 (queeeef, main a11c8f7f7): FATAL: unhandled exception 0xc0000005 on thread 37164 'RtcNetworkThread' rip 0x00007ffcdb1ea2be eossdk-ps5-shipping.prx.guest.prx+0xdea2be rcx 0000000010000005 rsi 0000000000400000 r12 0000000010000005 The Mound PPSA26223 (xXPBSXx, main 0a8b1d32b): FATAL: unhandled exception 0xc0000005 on thread 'RtcNetworkThread', rip eossdk-ps5-shipping.prx.guest.prx+0xefc6f4, rcx = 0x10000005, rsi = 0x400000 Dying Light 2 PPSA02262 (tv7): FATAL: unhandled exception 0xc0000005 on thread 'RtcNetworkThread' rcx 0000000010000005 ... rsi 0000000000400000 ... r12 0000000010000005 On FreeBSD 11.4 (8c639945) FD_SETSIZE is 1024 (sys/sys/select.h:58-59) and sockets take the lowest free entry of the one process descriptor table that files use (kern_descrip.c:1643-1660 fdalloc, uipc_syscalls.c kern_socket through falloc). Every socket number is now a real host descriptor: a zero-access NUL handle registered with _open_osfhandle on Windows (as directory descriptors already are) and an F_DUPFD_CLOEXEC duplicate of the host socket on Linux. The socket table maps that number to the socket, so "is this a socket" is a table lookup instead of a range check at the 31 former FirstDescriptor comparisons. close releases both the socket entry and the number, dup and dup2 duplicate the number through the host table and keep the socket shared, dup2 of a file onto a socket number drops the socket entry, and sceKernelClose now closes sockets too. sceNetSocket and sceNetAccept take their ids from the same table instead of a counter from 32, so libkernel and libSceNet numbers never collide. select polls guest sockets alongside libSceNet sockets, the random device, stdio and (on Linux) host descriptors; poll throws only for an open descriptor that is not a guest socket and reports POLLNVAL otherwise. File calls that used to fail with EBADF on the socket range return FreeBSD's answer for a socket: lseek ESPIPE (kern_lseek), ftruncate and getdirentries EINVAL (invfo_truncate, getvnode), flock EOPNOTSUPP; fstat on a socket throws. pread on a closed descriptor returns EBADF. title failing on host descriptor numbers; these three titles are that case, and this change keeps host numbering for files and only moves sockets into it. Tests: guest_socket_descriptors (new: socket below 1024, select on a socket and a file together, close frees the number for a file, dup of a socket, sceKernelClose), guest_select (socket and pipe together, Linux), guest_poll, guest_dup, guest_socket_pair, guest_fstatfs, guest_posix_filesystem and guest_sce_net updated. Co-Authored-By: Claude Fable 5.1 (cherry picked from commit 20378375b31eb5db922db6e55f45e44a4dae6445) --- core/libs/prx/libSceNet/Export.cpp | 8 +- .../prx/libScePosixForWebKit/Filesystem.cpp | 4 +- core/libs/prx/libkernel/CMakeLists.txt | 10 + core/libs/prx/libkernel/Equeue/Equeue.cpp | 6 +- core/libs/prx/libkernel/File/src/Open.cpp | 16 +- core/libs/prx/libkernel/File/src/Stdio.cpp | 67 +++--- core/libs/prx/libkernel/File/src/Terminal.cpp | 7 +- .../libkernel/Socket/include/SocketPoll.hpp | 2 + .../Socket/include/SocketRuntime.hpp | 4 +- core/libs/prx/libkernel/Socket/src/Socket.cpp | 155 ++++++------- core/libs/prx/libkernel/Socket/src/Udp.cpp | 203 ++++++++++++++---- core/libs/tests/GuestDup.cpp | 29 +-- core/libs/tests/GuestFstatfs.cpp | 4 +- core/libs/tests/GuestPoll.cpp | 14 +- core/libs/tests/GuestPosixFilesystem.cpp | 3 +- core/libs/tests/GuestSceNet.cpp | 15 +- core/libs/tests/GuestSelect.cpp | 44 ++++ core/libs/tests/GuestSocketDescriptors.cpp | 185 ++++++++++++++++ core/libs/tests/GuestSocketPair.cpp | 4 +- docs/dev/debt/libkernel.md | 10 +- 20 files changed, 584 insertions(+), 206 deletions(-) create mode 100644 core/libs/tests/GuestSocketDescriptors.cpp diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index 1ea3b505a..93f56d7b5 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -620,8 +620,9 @@ int APS5_VABI sceNetSocket(const char* name, int family, int type, int protocol) close_socket(native); return fail(12); } + const int fd = KernelReserveSocketNumber_nid_no_patch(static_cast(handle->value)); + if (fd < 0) return fail(-fd); std::lock_guard lk(g_mutex); - const int fd = g_next_sock++; Sock s; s.family = family; s.type = type; @@ -639,6 +640,7 @@ int APS5_VABI sceNetSocketClose(int s) { native = it->second.native; g_socks.erase(it); } + KernelReleaseSocketNumber_nid_no_patch(s); if (native) { #ifdef _WIN32 ::shutdown(native->value, SD_BOTH); @@ -735,10 +737,10 @@ int APS5_VABI sceNetAccept(int s, void* addr, uint32_t* addrlen) { connection.family = listener.family; connection.type = NET_SOCK_STREAM; connection.native = std::move(handle); - int descriptor; + const int descriptor = KernelReserveSocketNumber_nid_no_patch(static_cast(connection.native->value)); + if (descriptor < 0) return fail(-descriptor); { std::lock_guard lk(g_mutex); - descriptor = g_next_sock++; g_socks.emplace(descriptor, connection); } if (addr && !native_to_guest_address(peer, addr, addrlen)) { diff --git a/core/libs/prx/libScePosixForWebKit/Filesystem.cpp b/core/libs/prx/libScePosixForWebKit/Filesystem.cpp index b5245f5b1..a7e6f0175 100644 --- a/core/libs/prx/libScePosixForWebKit/Filesystem.cpp +++ b/core/libs/prx/libScePosixForWebKit/Filesystem.cpp @@ -62,8 +62,8 @@ extern "C" int APS5_VABI mkstemp_nid_postfix(char* pattern) { } extern "C" int APS5_VABI isatty_nid_postfix(int descriptor) { - if (descriptor >= GuestSockets::FirstDescriptor) { - Failure(GuestSocketIsOpen_nid_no_patch(descriptor) ? 25 : 9); + if (GuestSocketIsOpen_nid_no_patch(descriptor)) { + Failure(25); return 0; } #ifdef _WIN32 diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 606159fa6..36bd53063 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -1043,6 +1043,16 @@ if(BUILD_TESTING) set_tests_properties(guest_kqueue PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_socket_descriptors_tests ${LIBS_INCLUDE_DIR}/tests/GuestSocketDescriptors.cpp) + target_include_directories(guest_socket_descriptors_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_socket_descriptors_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_socket_descriptors_tests) + add_test(NAME guest_socket_descriptors COMMAND guest_socket_descriptors_tests) + set_tests_properties(guest_socket_descriptors PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_socket_descriptors 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 f94a13d02..641deca1d 100644 --- a/core/libs/prx/libkernel/Equeue/Equeue.cpp +++ b/core/libs/prx/libkernel/Equeue/Equeue.cpp @@ -2,6 +2,7 @@ #include "prx/libkernel/Time/include/Time.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/Socket/include/SocketRuntime.hpp" +#include "prx/libkernel/File/include/File.hpp" #include #include @@ -593,13 +594,14 @@ int APS5_VABI sceKernelDeleteAmprSystemEvent(KernelEqueue eq, int id) { } static int AddDescriptorEvent(KernelEqueue eq, int fd, std::size_t size, void* udata, int16_t filter) { - if (fd < GuestSockets::FirstDescriptor) { + const bool isSocket = GuestSockets::IsOpen(fd); + if (!isSocket && DescriptorIsOpen_nid_no_patch(fd)) { NotImplemented_nid_no_patch(filter == EVFILT_READ ? "sceKernelAddReadEvent: non-socket descriptor" : "sceKernelAddWriteEvent: non-socket descriptor"); } if (size > 1) { NotImplemented_nid_no_patch(filter == EVFILT_READ ? "sceKernelAddReadEvent: low watermark" : "sceKernelAddWriteEvent: low watermark"); } - if (!GuestSockets::IsOpen(fd)) { + if (!isSocket) { return SCE_KERNEL_ERROR_EBADF; } return EqueueAddEvent_nid_postfix(eq, EqueueDescriptorEvent(fd, filter, EV_ADD, udata)); diff --git a/core/libs/prx/libkernel/File/src/Open.cpp b/core/libs/prx/libkernel/File/src/Open.cpp index 2a1edd018..b08b33edb 100644 --- a/core/libs/prx/libkernel/File/src/Open.cpp +++ b/core/libs/prx/libkernel/File/src/Open.cpp @@ -221,7 +221,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()); + if (GuestSockets::IsOpen(d)) return GuestSockets::Close(d) == 0 ? 0 : SceKernelError(*__error_nid_postfix()); EqueueDescriptorClosed(d); File::ForgetRandomDevice(d); File::ForgetDescriptor(d); @@ -238,7 +238,7 @@ int APS5_VABI sceKernelClose(int d) { } std::int64_t APS5_VABI sceKernelRead(int d, void* buf, std::size_t nbytes) { - if (d >= GuestSockets::FirstDescriptor) { + if (GuestSockets::IsOpen(d)) { const auto n = GuestSockets::Read(d, buf, nbytes); return n < 0 ? SceKernelError(*__error_nid_postfix()) : n; } @@ -254,7 +254,7 @@ std::int64_t APS5_VABI sceKernelRead(int d, void* buf, std::size_t nbytes) { } std::int64_t APS5_VABI sceKernelWrite(int d, const void* buf, std::size_t nbytes) { - if (d >= GuestSockets::FirstDescriptor) { + if (GuestSockets::IsOpen(d)) { const auto n = GuestSockets::Write(d, buf, nbytes); return n < 0 ? SceKernelError(*__error_nid_postfix()) : n; } @@ -267,6 +267,7 @@ std::int64_t APS5_VABI sceKernelWrite(int d, const void* buf, std::size_t nbytes } std::int64_t APS5_VABI sceKernelLseek(int d, std::int64_t offset, int whence) { + if (GuestSockets::IsOpen(d)) return SCE_KERNEL_ERROR_ESPIPE; if (whence < 0 || whence > 2) { throw std::invalid_argument(std::string(__func__) + ": invalid whence=" + std::to_string(whence)); } @@ -340,9 +341,12 @@ int APS5_VABI sceKernelFcntl(int d, int command, ...) { #endif } int result; - if (d < GuestSockets::FirstDescriptor) result = File::Fcntl(d, command, argument); - else if (command == GuestSetFlags) result = fcntl_nid_postfix(d, command, static_cast(argument)); - else result = fcntl_nid_postfix(d, command); + if (GuestSockets::IsOpen(d)) { + if (command == GuestSetFlags) result = fcntl_nid_postfix(d, command, static_cast(argument)); + else result = fcntl_nid_postfix(d, command); + } else { + result = File::Fcntl(d, command, argument); + } return result < 0 ? SceKernelError(*__error_nid_postfix()) : result; } diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index f42248503..e7b833dd1 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -302,17 +302,17 @@ static constexpr int GUEST_EDEADLK = 11; static constexpr int GUEST_ELOOP = 62; static constexpr int GUEST_EDQUOT = 69; static constexpr int GUEST_EOVERFLOW = 84; +static constexpr int GUEST_EOPNOTSUPP = 45; static int SceErrorFromErrno(int error); static std::int64_t PositionedGuestSocketError(int descriptor) { - if (descriptor < GuestSockets::FirstDescriptor) return 0; - return SceErrorFromErrno(GuestSockets::IsOpen(descriptor) ? GUEST_ESPIPE : GUEST_EBADF); + return GuestSockets::IsOpen(descriptor) ? SceErrorFromErrno(GUEST_ESPIPE) : 0; } static bool FailPosixPositionedGuestSocket(int descriptor) { - if (descriptor < GuestSockets::FirstDescriptor) return false; - errno = GuestSockets::IsOpen(descriptor) ? GUEST_ESPIPE : GUEST_EBADF; + if (!GuestSockets::IsOpen(descriptor)) return false; + errno = GUEST_ESPIPE; return true; } @@ -491,13 +491,13 @@ int APS5_VABI chmod_nid_postfix(const char* path, int mode) { } int APS5_VABI close_nid_postfix(int d) { - if (d >= GuestSockets::FirstDescriptor) return GuestSockets::Close(d); int socketError = 0; if (KernelCloseSocket_nid_no_patch(d, &socketError)) { if (socketError == 0) return 0; *__error_nid_postfix() = socketError; return -1; } + if (GuestSockets::IsOpen(d)) return GuestSockets::Close(d); EqueueDescriptorClosed(d); File::ForgetRandomDevice(d); File::ForgetDescriptor(d); @@ -544,7 +544,7 @@ int File::HostStandardError() { extern "C" { int APS5_VABI dup_nid_postfix(int d) { - if (d >= GuestSockets::FirstDescriptor) return GuestSockets::Duplicate(d); + if (GuestSockets::IsOpen(d)) 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 @@ -554,8 +554,6 @@ int APS5_VABI dup_nid_postfix(int d) { const int duplicate = ::dup(d); #endif if (duplicate < 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff); - if (duplicate >= GuestSockets::FirstDescriptor) - throw std::runtime_error(std::string(__func__) + ": host descriptor reached the guest socket range"); if (File::IsRandomDevice(d)) File::RememberRandomDevice(duplicate); File::ShareDescriptor(d, duplicate); DuplicateJitSharedMemory(d, duplicate); @@ -564,15 +562,18 @@ int APS5_VABI dup_nid_postfix(int d) { int APS5_VABI dup2_nid_postfix(int from, int to) { if (from < 0 || to < 0) return PosixFailure(GUEST_EBADF); - const bool socketFrom = from >= GuestSockets::FirstDescriptor; - const bool socketTo = to >= GuestSockets::FirstDescriptor; - if (socketFrom && socketTo) return GuestSockets::DuplicateTo(from, to); - if (socketFrom) { - if (!GuestSockets::IsOpen(from)) return PosixFailure(GUEST_EBADF); - throw std::runtime_error(std::string(__func__) + ": moving a guest socket onto host descriptor " + std::to_string(to) + " is not supported"); + if (GuestSockets::IsOpen(from)) { + if (from != to && to <= 2) + throw std::runtime_error(std::string(__func__) + ": moving a guest socket onto standard descriptor " + std::to_string(to) + " is not supported"); + if (GuestSockets::DuplicateTo(from, to) < 0) return -1; + if (from == to) return to; + File::ForgetRandomDevice(to); +#ifdef _WIN32 + File::ForgetDirectoryDescriptor(to); + File::ForgetFileLock(to); +#endif + return 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 @@ -581,6 +582,7 @@ int APS5_VABI dup2_nid_postfix(int from, int to) { if (to == 2) PreserveHostStandardError(); if (WithoutParameterHandler([from, to] { return ::_dup2(from, to); }) != 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff); EqueueDescriptorClosed(to); + GuestSockets::Forget(to); File::ForgetDirectoryDescriptor(to); File::DuplicateDirectoryDescriptor(from, to); File::ForgetFileLock(to); @@ -598,6 +600,7 @@ int APS5_VABI dup2_nid_postfix(int from, int to) { const int result = ::dup2(from, to); if (result < 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff); EqueueDescriptorClosed(to); + GuestSockets::Forget(to); File::ForgetRandomDevice(to); if (sourceIsRandom) File::RememberRandomDevice(to); File::ShareDescriptor(from, to); @@ -607,6 +610,7 @@ int APS5_VABI dup2_nid_postfix(int from, int to) { } int APS5_VABI flock_nid_postfix(int d, int operation) { + if (GuestSockets::IsOpen(d)) return PosixFailure(GUEST_EOPNOTSUPP); const int type = operation & 8 ? 8 : operation & 2 ? 2 : operation & 1 ? 1 : 0; if (type == 0) return PosixFailure(GUEST_EBADF); if (NativeFlock(d, type | (operation & 4)) != 0) { @@ -624,7 +628,7 @@ int APS5_VABI flock_nid_postfix(int d, int operation) { } int APS5_VABI _fstatfs_nid_postfix(int d, KernelStatfs* buf) { - if (d >= GuestSockets::FirstDescriptor) return PosixFailure(GuestSockets::IsOpen(d) ? GUEST_EINVAL : GUEST_EBADF); + if (GuestSockets::IsOpen(d)) return PosixFailure(GUEST_EINVAL); if (d < 0) return PosixFailure(GUEST_EBADF); KernelStatfs result{}; result.f_version = KERNEL_STATFS_VERSION; @@ -640,12 +644,13 @@ int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int d, FileStat* sb) { if (sb == nullptr) { APS5_INVALID_ARG_EX; } + if (GuestSockets::IsOpen(d)) throw std::runtime_error(std::string(__func__) + ": the status of a guest socket is not implemented"); File::FillFileStat(d, sb); return 0; } int APS5_VABI ftruncate_nid_postfix(int d, int64_t length) { - if (length < 0) { + if (length < 0 || GuestSockets::IsOpen(d)) { return PosixFailure(GUEST_EINVAL); } #ifdef _WIN32 @@ -723,6 +728,7 @@ int64_t APS5_VABI pread_nid_postfix(int d, void* buf, size_t nbytes, int64_t off if (!destination.Open()) errno = EFAULT; else if (File::ReadRandomDevice(d, buf, nbytes)) return static_cast(nbytes); auto n = destination.Open() ? NativePread(d, buf, nbytes, offset) : -1; + if (n < 0 && errno == EBADF) return PosixFailure(GUEST_EBADF); if (n < 0) { throw std::runtime_error(std::string(__func__) + ": pread failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno)); } @@ -739,14 +745,14 @@ int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int d, const void* buf, size_ } int64_t APS5_VABI read_nid_postfix(int d, void* buf, uint64_t nbytes) { - if (d >= GuestSockets::FirstDescriptor) return GuestSockets::Read(d, buf, static_cast(nbytes)); + if (GuestSockets::IsOpen(d)) return GuestSockets::Read(d, buf, static_cast(nbytes)); if (buf == nullptr && nbytes != 0) return PosixFailure(GUEST_EFAULT); char emptyBuffer = 0; return PosixResult(sceKernelRead(d, buf == nullptr ? &emptyBuffer : buf, static_cast(nbytes))); } std::int64_t APS5_VABI _read_nid_postfix(int descriptor, void* buffer, std::size_t count) { - if (descriptor >= GuestSockets::FirstDescriptor) return GuestSockets::Read(descriptor, buffer, count); + if (GuestSockets::IsOpen(descriptor)) return GuestSockets::Read(descriptor, buffer, count); if (buffer == nullptr && count != 0) return PosixFailure(GUEST_EFAULT); char emptyBuffer = 0; return PosixResult(sceKernelRead(descriptor, buffer == nullptr ? &emptyBuffer : buffer, count)); @@ -756,14 +762,14 @@ int64_t APS5_VABI write_nid_postfix(int d, const char* str, int64_t size) { if (size < 0) { APS5_INVALID_ARG_EX; } - if (d >= GuestSockets::FirstDescriptor) return GuestSockets::Write(d, str, static_cast(size)); + if (GuestSockets::IsOpen(d)) return GuestSockets::Write(d, str, static_cast(size)); if (str == nullptr && size != 0) return PosixFailure(GUEST_EFAULT); const char emptyBuffer = 0; return PosixResult(sceKernelWrite(d, str == nullptr ? &emptyBuffer : str, static_cast(size))); } std::int64_t APS5_VABI _write_nid_postfix(int descriptor, const void* buffer, std::size_t count) { - if (descriptor >= GuestSockets::FirstDescriptor) return GuestSockets::Write(descriptor, buffer, count); + if (GuestSockets::IsOpen(descriptor)) return GuestSockets::Write(descriptor, buffer, count); if (buffer == nullptr && count != 0) return PosixFailure(GUEST_EFAULT); const char emptyBuffer = 0; return PosixResult(sceKernelWrite(descriptor, buffer == nullptr ? &emptyBuffer : buffer, count)); @@ -801,12 +807,13 @@ int APS5_VABI sceKernelCheckReachability(const char* path) { int APS5_VABI sceKernelFstat(int d, FileStat* sb) { if (sb == nullptr) throw std::invalid_argument("sceKernelFstat: sb is null"); + if (GuestSockets::IsOpen(d)) throw std::runtime_error("sceKernelFstat: the status of a guest socket is not implemented"); if (!File::FillFileStatFromDescriptor(d, sb)) return SceErrorFromErrno(errno); return 0; } int APS5_VABI sceKernelFsync(int fd) { - if (fd >= GuestSockets::FirstDescriptor) return SceErrorFromErrno(GuestSockets::IsOpen(fd) ? GUEST_EINVAL : GUEST_EBADF); + if (GuestSockets::IsOpen(fd)) return SceErrorFromErrno(GUEST_EINVAL); #ifdef _WIN32 if (NativeSync(fd) != 0) return SceErrorFromErrno(errno); #else @@ -816,7 +823,7 @@ int APS5_VABI sceKernelFsync(int fd) { } int APS5_VABI sceKernelFdatasync(int fd) { - if (fd >= GuestSockets::FirstDescriptor) return SceErrorFromErrno(GuestSockets::IsOpen(fd) ? GUEST_EINVAL : GUEST_EBADF); + if (GuestSockets::IsOpen(fd)) return SceErrorFromErrno(GUEST_EINVAL); #ifdef _WIN32 if (NativeSync(fd) != 0) return SceErrorFromErrno(errno); #else @@ -837,6 +844,7 @@ int APS5_VABI sceKernelWriteThrottlingStatus(std::uint64_t* status) { #ifdef _WIN32 int APS5_VABI sceKernelGetdirentries(int fd, char* buf, int nbytes, int64_t* basep) { + if (GuestSockets::IsOpen(fd)) return SceErrorFromErrno(GUEST_EINVAL); if (buf == nullptr) return SceErrorFromErrno(GUEST_EFAULT); if (nbytes <= 0) return SceErrorFromErrno(GUEST_EINVAL); if (basep != nullptr) *basep = 0; @@ -850,6 +858,7 @@ int APS5_VABI sceKernelGetdents(int fd, char* buf, int nbytes) { #else int APS5_VABI sceKernelGetdirentries(int fd, char* buf, int nbytes, int64_t* basep) { + if (GuestSockets::IsOpen(fd)) return SceErrorFromErrno(GUEST_EINVAL); constexpr std::size_t GuestHeaderBytes = 8; constexpr std::size_t GuestMaxName = 255; if (buf == nullptr) return SceErrorFromErrno(GUEST_EFAULT); @@ -980,7 +989,7 @@ static std::int64_t TransferIovecs(int d, const KernelIovec* iov, int iovcnt, co } std::deque destinations; if (!write && !OpenIovecs(iov, iovcnt, destinations)) return SceErrorFromErrno(GUEST_EFAULT); - if (offset == nullptr && d >= GuestSockets::FirstDescriptor) { + if (offset == nullptr && GuestSockets::IsOpen(d)) { const auto result = write ? GuestSockets::Writev(d, reinterpret_cast(iov), iovcnt) : GuestSockets::Readv(d, reinterpret_cast(iov), iovcnt); return result < 0 ? SceKernelError(*__error_nid_postfix()) : result; @@ -1048,7 +1057,7 @@ int64_t APS5_VABI sceKernelReadv(int d, const KernelIovec* iov, int iovcnt) { if (const int error = CheckIovecs(iov, iovcnt)) return error; std::deque destinations; if (!OpenIovecs(iov, iovcnt, destinations)) return SceErrorFromErrno(GUEST_EFAULT); - if (d >= GuestSockets::FirstDescriptor) { + if (GuestSockets::IsOpen(d)) { const auto result = GuestSockets::Readv(d, reinterpret_cast(iov), iovcnt); return result < 0 ? SceKernelError(*__error_nid_postfix()) : result; } @@ -1059,7 +1068,7 @@ int64_t APS5_VABI sceKernelReadv(int d, const KernelIovec* iov, int iovcnt) { int64_t APS5_VABI sceKernelWritev(int d, const KernelIovec* iov, int iovcnt) { if (const int error = CheckIovecs(iov, iovcnt)) return error; - if (d >= GuestSockets::FirstDescriptor) { + if (GuestSockets::IsOpen(d)) { const auto result = GuestSockets::Writev(d, reinterpret_cast(iov), iovcnt); return result < 0 ? SceKernelError(*__error_nid_postfix()) : result; } @@ -1155,7 +1164,7 @@ int APS5_VABI sceKernelChmod_nid_postfix(const char* path, std::uint16_t mode) { } int APS5_VABI sceKernelFchmod(int d, std::uint16_t mode) { - if (d >= GuestSockets::FirstDescriptor) return SceErrorFromErrno(GuestSockets::IsOpen(d) ? GUEST_EINVAL : GUEST_EBADF); + if (GuestSockets::IsOpen(d)) return SceErrorFromErrno(GUEST_EINVAL); if (NativeFchmod(d, mode & 07777) != 0) return SceErrorFromErrno(errno); return 0; } @@ -1204,7 +1213,7 @@ int APS5_VABI utimes_nid_postfix(const char* path, const KernelTimeval* times) { } int APS5_VABI sceKernelFutimes(int d, const KernelTimeval* times) { - if (d >= GuestSockets::FirstDescriptor) return SceErrorFromErrno(GuestSockets::IsOpen(d) ? GUEST_EINVAL : GUEST_EBADF); + if (GuestSockets::IsOpen(d)) return SceErrorFromErrno(GUEST_EINVAL); if (times != nullptr) { for (int i = 0; i < 2; ++i) { if (times[i].tv_usec < 0 || times[i].tv_usec >= 1000000) return SceErrorFromErrno(GUEST_EINVAL); diff --git a/core/libs/prx/libkernel/File/src/Terminal.cpp b/core/libs/prx/libkernel/File/src/Terminal.cpp index 229ff48a5..648c63eb7 100644 --- a/core/libs/prx/libkernel/File/src/Terminal.cpp +++ b/core/libs/prx/libkernel/File/src/Terminal.cpp @@ -59,11 +59,8 @@ bool HostPipe(int descriptor) { #endif int TerminalError(int descriptor) { - if (descriptor >= GuestSockets::FirstDescriptor) { - const int family = GuestSockets::Family(descriptor); - if (family < 0) return BadDescriptor; - return family == InternetFamily ? AddressNotAvailable : OperationNotSupported; - } + const int family = GuestSockets::Family(descriptor); + if (family >= 0) return family == InternetFamily ? AddressNotAvailable : OperationNotSupported; if (descriptor < 0 || !HostOpen(descriptor)) return BadDescriptor; return NotTerminal; } diff --git a/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp b/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp index c990c6686..622a17bd2 100644 --- a/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp +++ b/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp @@ -25,3 +25,5 @@ extern "C" void KernelSetSocketCloser_nid_no_patch(KernelSocketPoll::Closer clos extern "C" bool KernelCloseSocket_nid_no_patch(int descriptor, int* error); extern "C" void KernelSetSocketOptionSetter_nid_no_patch(KernelSocketPoll::OptionSetter setter); extern "C" bool KernelSetSocketOption_nid_no_patch(int descriptor, int level, int option, const void* value, std::uint32_t length, int* error); +extern "C" int KernelReserveSocketNumber_nid_no_patch(std::uintptr_t native); +extern "C" void KernelReleaseSocketNumber_nid_no_patch(int number); diff --git a/core/libs/prx/libkernel/Socket/include/SocketRuntime.hpp b/core/libs/prx/libkernel/Socket/include/SocketRuntime.hpp index 41bf8caf3..103f2d068 100644 --- a/core/libs/prx/libkernel/Socket/include/SocketRuntime.hpp +++ b/core/libs/prx/libkernel/Socket/include/SocketRuntime.hpp @@ -2,8 +2,8 @@ #include #include #include +#include "prx/libkernel/Socket/include/SocketPoll.hpp" namespace GuestSockets { -constexpr int FirstDescriptor = 0x10000000; struct Iovec { void* base; std::size_t length; @@ -11,11 +11,13 @@ struct Iovec { int Duplicate(int descriptor); int DuplicateTo(int descriptor, int target); int Close(int descriptor); +void Forget(int descriptor); bool IsOpen(int descriptor); int Family(int descriptor); std::int64_t Read(int descriptor, void* buffer, std::size_t length); std::int64_t Write(int descriptor, const void* buffer, std::size_t length); bool Ready(int descriptor, bool write, std::int64_t* data, bool* eof); +int Poll(KernelSocketPoll::Entry* entries, int count, int timeoutMilliseconds); struct Interest { int descriptor; bool write; diff --git a/core/libs/prx/libkernel/Socket/src/Socket.cpp b/core/libs/prx/libkernel/Socket/src/Socket.cpp index f92f2d013..5082e538e 100644 --- a/core/libs/prx/libkernel/Socket/src/Socket.cpp +++ b/core/libs/prx/libkernel/Socket/src/Socket.cpp @@ -47,6 +47,16 @@ void ClearDescriptor(void* set, int descriptor) { if (set != nullptr) static_cast(set)[descriptor / 64] &= ~(std::uint64_t{1} << (descriptor % 64)); } +struct PollGroup { + KernelSocketPoll::Poller poll; + std::vector entries; + std::vector slots; +}; + +int KernelSockets(KernelSocketPoll::Entry* entries, int count, int timeoutMilliseconds) { + return GuestSockets::Poll(entries, count, timeoutMilliseconds); +} + #ifndef _WIN32 short HostEventFlags(short events) { @@ -65,20 +75,17 @@ short KernelEventFlags(short revents) { return flags; } -int HostPoll(KernelSocketPoll::Entry* descriptors, std::size_t count, int timeoutMilliseconds) { - std::vector native(count); - for (std::size_t index = 0; index < count; ++index) { +int HostPoll(KernelSocketPoll::Entry* descriptors, int count, int timeoutMilliseconds) { + std::vector native(static_cast(count)); + for (std::size_t index = 0; index < native.size(); ++index) { native[index].fd = descriptors[index].descriptor; - native[index].events = descriptors[index].events; + native[index].events = HostEventFlags(descriptors[index].events); native[index].revents = 0; } - const int result = ::poll(native.data(), static_cast(count), timeoutMilliseconds); - if (result < 0) return -1; - for (std::size_t index = 0; index < count; ++index) { - if ((native[index].revents & POLLNVAL) != 0) { - errno = EBADF; - return -1; - } + const int result = ::poll(native.data(), static_cast(native.size()), timeoutMilliseconds); + if (result < 0) return -errno; + for (std::size_t index = 0; index < native.size(); ++index) { + if ((native[index].revents & POLLNVAL) != 0) return -EBADF; descriptors[index].revents = KernelEventFlags(native[index].revents); } return result; @@ -131,10 +138,8 @@ int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* *__error_nid_postfix() = GuestEinval; return -1; } - std::vector sockets; - std::vector socketSlots; - std::vector hosts; - std::vector hostSlots; + PollGroup kernel{&KernelSockets, {}, {}}; + PollGroup net{nullptr, {}, {}}; std::vector devices; std::vector requested; for (int descriptor = 0; descriptor < nfds; ++descriptor) { @@ -143,57 +148,50 @@ int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* if (DescriptorSet(writefds, descriptor)) events |= KernelSocketPoll::Writable; if (DescriptorSet(exceptfds, descriptor)) events |= KernelSocketPoll::Urgent; if (events == 0) continue; - if (File::IsRandomDevice(descriptor)) devices.push_back({descriptor, events, static_cast(events & (KernelSocketPoll::Readable | KernelSocketPoll::Writable))}); + if (GuestSockets::IsOpen(descriptor)) { + kernel.entries.push_back({descriptor, events, 0}); + kernel.slots.push_back(requested.size()); + requested.push_back({descriptor, events, 0}); + } else if (File::IsRandomDevice(descriptor)) devices.push_back({descriptor, events, static_cast(events & (KernelSocketPoll::Readable | KernelSocketPoll::Writable))}); else if (descriptor <= 2) devices.push_back({descriptor, events, static_cast(descriptor == 0 ? 0 : events & KernelSocketPoll::Writable)}); else { - sockets.push_back({descriptor, events, 0}); - socketSlots.push_back(requested.size()); + net.entries.push_back({descriptor, events, 0}); + net.slots.push_back(requested.size()); requested.push_back({descriptor, events, 0}); } } for (const auto& device : devices) requested.push_back(device); const bool devicesReady = std::any_of(devices.begin(), devices.end(), [](const auto& entry) { return entry.revents != 0; }); - const auto poller = g_socketPoller.load(); - if (!sockets.empty()) { - if (poller == nullptr) { - for (std::size_t index = 0; index < sockets.size();) { - const int descriptor = sockets[index].descriptor; + std::vector groups; + if (!kernel.entries.empty()) groups.push_back(std::move(kernel)); + if (!net.entries.empty()) { + PollGroup hosts{nullptr, {}, {}}; #ifndef _WIN32 - if (descriptor < GuestSockets::FirstDescriptor) { - hosts.push_back({descriptor, HostEventFlags(sockets[index].events), 0}); - hostSlots.push_back(socketSlots[index]); - sockets.erase(sockets.begin() + static_cast(index)); - socketSlots.erase(socketSlots.begin() + static_cast(index)); - continue; - } + hosts.poll = &HostPoll; #endif - UnknownDescriptor(descriptor, false); - } - } else { - for (auto& socket : sockets) socket.revents = 0; - const int probe = poller(sockets.data(), static_cast(sockets.size()), 0); + net.poll = g_socketPoller.load(); + if (net.poll != nullptr) { + for (auto& socket : net.entries) socket.revents = 0; + const int probe = net.poll(net.entries.data(), static_cast(net.entries.size()), 0); if (probe < 0) { *__error_nid_postfix() = -probe; return -1; } - for (std::size_t index = 0; index < sockets.size();) { - if ((sockets[index].revents & KernelSocketPoll::Unknown) == 0) { - ++index; - continue; - } - const int descriptor = sockets[index].descriptor; -#ifndef _WIN32 - if (descriptor < GuestSockets::FirstDescriptor) { - hosts.push_back({descriptor, HostEventFlags(sockets[index].events), 0}); - hostSlots.push_back(socketSlots[index]); - sockets.erase(sockets.begin() + static_cast(index)); - socketSlots.erase(socketSlots.begin() + static_cast(index)); - continue; - } -#endif - UnknownDescriptor(descriptor, true); - } } + for (std::size_t index = 0; index < net.entries.size();) { + if (net.poll != nullptr && (net.entries[index].revents & KernelSocketPoll::Unknown) == 0) { + ++index; + continue; + } + const int descriptor = net.entries[index].descriptor; + if (hosts.poll == nullptr) UnknownDescriptor(descriptor, net.poll != nullptr); + hosts.entries.push_back({descriptor, net.entries[index].events, 0}); + hosts.slots.push_back(net.slots[index]); + net.entries.erase(net.entries.begin() + static_cast(index)); + net.slots.erase(net.slots.begin() + static_cast(index)); + } + if (!net.entries.empty()) groups.push_back(std::move(net)); + if (!hosts.entries.empty()) groups.push_back(std::move(hosts)); } const auto start = std::chrono::steady_clock::now(); const auto deadline = limit == nullptr ? std::chrono::steady_clock::time_point::max() @@ -206,48 +204,25 @@ int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* waitMilliseconds = static_cast(std::clamp(roundedMilliseconds, 0, 100)); } if (devicesReady) waitMilliseconds = 0; -#ifndef _WIN32 - if (!hosts.empty() && !sockets.empty()) { - if (poller == nullptr) UnknownDescriptor(sockets.front().descriptor, false); + if (groups.empty()) { + if (waitMilliseconds > 0) std::this_thread::sleep_for(std::chrono::milliseconds(waitMilliseconds)); + } else { + const int slice = groups.size() == 1 ? waitMilliseconds : std::min(waitMilliseconds, SelectPollSliceMilliseconds); const auto sliceEnd = std::chrono::steady_clock::now() + std::chrono::milliseconds(waitMilliseconds); for (;;) { - for (auto& host : hosts) host.revents = 0; - const int hostReady = HostPoll(hosts.data(), hosts.size(), SelectPollSliceMilliseconds); - if (hostReady < 0) { - *__error_nid_postfix() = errno; - return -1; + int groupReady = 0; + for (auto& group : groups) { + for (auto& entry : group.entries) entry.revents = 0; + const int result = group.poll(group.entries.data(), static_cast(group.entries.size()), slice); + if (result < 0) { + *__error_nid_postfix() = -result; + return -1; + } + for (std::size_t index = 0; index < group.entries.size(); ++index) requested[group.slots[index]].revents = group.entries[index].revents; + groupReady += result; } - for (std::size_t index = 0; index < hosts.size(); ++index) requested[hostSlots[index]].revents = hosts[index].revents; - for (auto& socket : sockets) socket.revents = 0; - const int socketReady = poller(sockets.data(), static_cast(sockets.size()), SelectPollSliceMilliseconds); - if (socketReady < 0) { - *__error_nid_postfix() = -socketReady; - return -1; - } - for (std::size_t index = 0; index < sockets.size(); ++index) requested[socketSlots[index]].revents = sockets[index].revents; - if (hostReady > 0 || socketReady > 0) break; - if (std::chrono::steady_clock::now() >= sliceEnd) break; + if (groups.size() == 1 || groupReady > 0 || std::chrono::steady_clock::now() >= sliceEnd) break; } - } else if (!hosts.empty()) { - for (auto& host : hosts) host.revents = 0; - if (HostPoll(hosts.data(), hosts.size(), waitMilliseconds) < 0) { - *__error_nid_postfix() = errno; - return -1; - } - for (std::size_t index = 0; index < hosts.size(); ++index) requested[hostSlots[index]].revents = hosts[index].revents; - } else -#endif - if (!sockets.empty()) { - if (poller == nullptr) UnknownDescriptor(sockets.front().descriptor, false); - for (auto& socket : sockets) socket.revents = 0; - const int result = poller(sockets.data(), static_cast(sockets.size()), waitMilliseconds); - if (result < 0) { - *__error_nid_postfix() = -result; - return -1; - } - for (std::size_t index = 0; index < sockets.size(); ++index) requested[socketSlots[index]].revents = sockets[index].revents; - } else if (waitMilliseconds > 0) { - std::this_thread::sleep_for(std::chrono::milliseconds(waitMilliseconds)); } int ready = 0; for (const auto& entry : requested) { diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index 08e39685e..c611762e1 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -2,6 +2,7 @@ #define WIN32_LEAN_AND_MEAN #include #include +#include #include #else #include @@ -129,7 +130,56 @@ Waker& ThreadWaker() { } std::mutex socketsMutex; std::map> sockets; -int nextDescriptor = GuestSockets::FirstDescriptor; +#ifdef _WIN32 +int ReserveNumber(NativeSocket) { + const HANDLE placeholder = ::CreateFileW(L"NUL", 0, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr); + if (placeholder == INVALID_HANDLE_VALUE) + throw std::runtime_error("socket: opening the NUL descriptor placeholder failed, error " + std::to_string(::GetLastError())); + const int number = ::_open_osfhandle(reinterpret_cast(placeholder), _O_RDONLY); + if (number < 0) { + ::CloseHandle(placeholder); + return Fail(24); + } + return number; +} +int CopyNumber(int number) { + const int copy = ::_dup(number); + return copy < 0 ? Fail(errno == EMFILE ? 24 : 9) : copy; +} +int CopyNumberTo(int number, int target) { + return ::_dup2(number, target) != 0 ? Fail(errno == EMFILE ? 24 : 9) : 0; +} +void ReleaseNumber(int number) { + ::_close(number); +} +#else +int ReserveNumber(NativeSocket native) { + const int number = ::fcntl(native, F_DUPFD_CLOEXEC, 0); + return number < 0 ? Fail(NativeError()) : number; +} +int CopyNumber(int number) { + const int copy = ::fcntl(number, F_DUPFD_CLOEXEC, 0); + return copy < 0 ? Fail(NativeError()) : copy; +} +int CopyNumberTo(int number, int target) { + return ::dup2(number, target) < 0 ? Fail(NativeError()) : 0; +} +void ReleaseNumber(int number) { + ::close(number); +} +#endif +int Install(std::shared_ptr socket) { + const int number = ReserveNumber(socket->value); + if (number < 0) return -1; + try { + std::lock_guard lock(socketsMutex); + sockets.insert_or_assign(number, std::move(socket)); + } catch (const std::bad_alloc&) { + ReleaseNumber(number); + return Fail(12); + } + return number; +} std::shared_ptr Lookup(int descriptor) { std::lock_guard lock(socketsMutex); const auto found = sockets.find(descriptor); @@ -221,14 +271,31 @@ void GuestAddress(const sockaddr_storage& native, void* output, std::uint32_t* l } int GuestSockets::Close(int descriptor) { + std::shared_ptr released; { std::lock_guard lock(socketsMutex); - if (!sockets.erase(descriptor)) return Fail(9); + const auto found = sockets.find(descriptor); + if (found == sockets.end()) return Fail(9); + released = std::move(found->second); + sockets.erase(found); } EqueueDescriptorClosed(descriptor); + ReleaseNumber(descriptor); return 0; } +void GuestSockets::Forget(int descriptor) { + std::shared_ptr released; + { + std::lock_guard lock(socketsMutex); + const auto found = sockets.find(descriptor); + if (found == sockets.end()) return; + released = std::move(found->second); + sockets.erase(found); + } + EqueueDescriptorClosed(descriptor); +} + std::uintptr_t GuestSockets::CurrentWaker() { return static_cast(ThreadWaker().value); } @@ -299,6 +366,15 @@ extern "C" bool GuestSocketIsOpen_nid_no_patch(int descriptor) { return GuestSockets::IsOpen(descriptor); } +extern "C" int KernelReserveSocketNumber_nid_no_patch(std::uintptr_t native) { + const int number = ReserveNumber(static_cast(native)); + return number < 0 ? -*__error_nid_postfix() : number; +} + +extern "C" void KernelReleaseSocketNumber_nid_no_patch(int number) { + ReleaseNumber(number); +} + namespace { #ifdef _WIN32 using NativePollDescriptor = WSAPOLLFD; @@ -389,33 +465,43 @@ bool IsPipe(int descriptor, short events) { } int GuestSockets::Duplicate(int descriptor) { + std::lock_guard lock(socketsMutex); + const auto found = sockets.find(descriptor); + if (found == sockets.end()) return Fail(9); + const int duplicate = CopyNumber(descriptor); + if (duplicate < 0) return -1; try { - std::lock_guard lock(socketsMutex); - const auto found = sockets.find(descriptor); - if (found == sockets.end()) return Fail(9); - if (nextDescriptor == INT_MAX) return Fail(24); - const int duplicate = nextDescriptor; - sockets.emplace(duplicate, found->second); - ++nextDescriptor; - return duplicate; + sockets.insert_or_assign(duplicate, found->second); } catch (const std::bad_alloc&) { + ReleaseNumber(duplicate); return Fail(12); } + return duplicate; } int GuestSockets::DuplicateTo(int descriptor, int target) { - try { + std::shared_ptr replaced; + { std::lock_guard lock(socketsMutex); const auto found = sockets.find(descriptor); if (found == sockets.end()) return Fail(9); if (target == descriptor) return target; - if (target == INT_MAX) return Fail(9); - sockets.insert_or_assign(target, found->second); - if (target >= nextDescriptor) nextDescriptor = target + 1; - return target; - } catch (const std::bad_alloc&) { - return Fail(12); + if (CopyNumberTo(descriptor, target) != 0) return -1; + const auto previous = sockets.find(target); + if (previous != sockets.end()) { + replaced = std::move(previous->second); + previous->second = found->second; + } else { + try { + sockets.emplace(target, found->second); + } catch (const std::bad_alloc&) { + ReleaseNumber(target); + return Fail(12); + } + } } + if (replaced) EqueueDescriptorClosed(target); + return target; } namespace { @@ -537,6 +623,51 @@ bool GuestSockets::Ready(int descriptor, bool write, std::int64_t* data, bool* e return true; } +int GuestSockets::Poll(KernelSocketPoll::Entry* entries, int count, int timeoutMilliseconds) { + std::vector> held; + std::vector native; +#ifdef _WIN32 + constexpr short NativeReadable = POLLRDNORM; + constexpr short NativeUrgent = POLLRDBAND; + constexpr short NativeWritable = POLLWRNORM; +#else + constexpr short NativeReadable = POLLIN; + constexpr short NativeUrgent = POLLPRI; + constexpr short NativeWritable = POLLOUT; +#endif + for (int index = 0; index < count; ++index) { + auto socket = Find(entries[index].descriptor); + if (!socket) return -9; + NativePollDescriptor request{}; + request.fd = socket->value; + if (entries[index].events & KernelSocketPoll::Readable) request.events = static_cast(request.events | NativeReadable); + if (entries[index].events & KernelSocketPoll::Urgent) request.events = static_cast(request.events | NativeUrgent); + if (entries[index].events & KernelSocketPoll::Writable) request.events = static_cast(request.events | NativeWritable); + native.push_back(request); + held.push_back(std::move(socket)); + } + if (native.empty()) return 0; +#ifdef _WIN32 + const int result = WSAPoll(native.data(), static_cast(native.size()), timeoutMilliseconds); +#else + const int result = ::poll(native.data(), static_cast(native.size()), timeoutMilliseconds); +#endif + if (result < 0) return -NativeError(); + int ready = 0; + for (int index = 0; index < count; ++index) { + const short events = native[static_cast(index)].revents; + short revents = 0; + if (events & NativeReadable) revents = static_cast(revents | KernelSocketPoll::Readable); + if (events & NativeUrgent) revents = static_cast(revents | KernelSocketPoll::Urgent); + if (events & NativeWritable) revents = static_cast(revents | KernelSocketPoll::Writable); + if (events & (POLLERR | POLLNVAL)) revents = static_cast(revents | KernelSocketPoll::Error); + if (events & POLLHUP) revents = static_cast(revents | KernelSocketPoll::HangUp); + entries[index].revents = revents; + if (revents != 0) ++ready; + } + return ready; +} + extern "C" { int APS5_VABI fcntl_nid_postfix(int descriptor, int command, ...) { if (descriptor < GuestSockets::FirstDescriptor) { @@ -636,11 +767,7 @@ int APS5_VABI socket_nid_postfix(int family, int type, int protocol) { auto socket = std::make_shared(native, family, plainType); guard.value = Invalid; if (nonblocking && SetNonBlocking(*socket)) return -1; - std::lock_guard lock(socketsMutex); - if (nextDescriptor == INT_MAX) return Fail(24); - const int descriptor = nextDescriptor++; - sockets.emplace(descriptor, std::move(socket)); - return descriptor; + return Install(std::move(socket)); } catch (const std::bad_alloc&) { return Fail(12); } @@ -669,16 +796,13 @@ int APS5_VABI socketpair_nid_postfix(int family, int type, int protocol, int* de auto second = std::make_shared(native[1], GuestUnix, plainType); secondGuard.value = Invalid; if (nonblocking && (SetNonBlocking(*first) || SetNonBlocking(*second))) return -1; - std::lock_guard lock(socketsMutex); - if (nextDescriptor >= INT_MAX - 1) return Fail(24); - const int firstDescriptor = nextDescriptor++; - const int secondDescriptor = nextDescriptor++; - sockets.emplace(firstDescriptor, std::move(first)); - try { - sockets.emplace(secondDescriptor, std::move(second)); - } catch (const std::bad_alloc&) { - sockets.erase(firstDescriptor); - return Fail(12); + const int firstDescriptor = Install(std::move(first)); + if (firstDescriptor < 0) return -1; + const int secondDescriptor = Install(std::move(second)); + if (secondDescriptor < 0) { + const int error = *__error_nid_postfix(); + GuestSockets::Close(firstDescriptor); + return Fail(error); } descriptors[0] = firstDescriptor; descriptors[1] = secondDescriptor; @@ -724,10 +848,8 @@ int APS5_VABI accept_nid_postfix(int descriptor, void* address, std::uint32_t* l inheritNonblocking = listener->nonblocking; } if (inheritNonblocking && SetNonBlocking(*accepted)) return -1; - std::lock_guard lock(socketsMutex); - if (nextDescriptor == INT_MAX) return Fail(24); - const int acceptedDescriptor = nextDescriptor++; - sockets.emplace(acceptedDescriptor, std::move(accepted)); + const int acceptedDescriptor = Install(std::move(accepted)); + if (acceptedDescriptor < 0) return -1; if (address) GuestAddress(peer, address, length); return acceptedDescriptor; } catch (const std::bad_alloc&) { @@ -1052,7 +1174,8 @@ int APS5_VABI poll_nid_postfix(GuestPollDescriptor* descriptors, std::uint32_t c if (entry.descriptor < 0) continue; if ((entry.events & ~GuestPollAccepted) != 0) throw std::runtime_error("poll: unsupported event flags " + std::to_string(entry.events)); - if (entry.descriptor < GuestSockets::FirstDescriptor) { + auto socket = Find(entry.descriptor); + if (!socket) { if (!DescriptorIsOpen_nid_no_patch(entry.descriptor)) { entry.revents = GuestPollInvalid; ++ready; @@ -1068,12 +1191,6 @@ int APS5_VABI poll_nid_postfix(GuestPollDescriptor* descriptors, std::uint32_t c pipes.push_back(i); continue; } - auto socket = Find(entry.descriptor); - if (!socket) { - entry.revents = GuestPollInvalid; - ++ready; - continue; - } #ifdef _WIN32 if (entry.events & GuestPollPriority) throw std::runtime_error("poll: POLLPRI is not supported by WSAPoll"); #endif diff --git a/core/libs/tests/GuestDup.cpp b/core/libs/tests/GuestDup.cpp index e4241dc52..04614f806 100644 --- a/core/libs/tests/GuestDup.cpp +++ b/core/libs/tests/GuestDup.cpp @@ -24,7 +24,8 @@ static void Require(bool value) { if (!value) std::abort(); } constexpr int GuestEbadf = 9; constexpr int Unix = 1; constexpr int Stream = 1; -constexpr int FirstSocket = 0x10000000; +constexpr int DescriptorSetSize = 1024; +constexpr int Unopened = 0x1ffffff0; static void RequireFailure(int result, int error) { Require(result == -1); @@ -60,7 +61,7 @@ static void DupOutlivesOriginal() { int pipe[2]; Require(pipe_nid_postfix(pipe) == 0); const int duplicate = dup_nid_postfix(pipe[1]); - Require(duplicate >= 0 && duplicate < FirstSocket && duplicate != pipe[1]); + Require(duplicate >= 0 && duplicate != pipe[1]); Require(close_nid_postfix(pipe[1]) == 0); RequirePipeCarries(duplicate, pipe[0], "dup"); Require(close_nid_postfix(duplicate) == 0); @@ -97,8 +98,8 @@ static void BadDescriptors() { RequireFailure(dup2_nid_postfix(closed, closed), GuestEbadf); RequireFailure(dup2_nid_postfix(pipe[0], -1), GuestEbadf); RequireFailure(dup2_nid_postfix(-1, pipe[0]), GuestEbadf); - RequireFailure(dup_nid_postfix(FirstSocket + 0x0ffffff0), GuestEbadf); - RequireFailure(dup2_nid_postfix(FirstSocket + 0x0ffffff0, FirstSocket + 0x0ffffff1), GuestEbadf); + RequireFailure(dup_nid_postfix(Unopened), GuestEbadf); + RequireFailure(dup2_nid_postfix(Unopened, Unopened + 1), GuestEbadf); #ifdef _WIN32 const auto previous = _set_invalid_parameter_handler(RecordInvalidParameter); #endif @@ -115,25 +116,29 @@ static void SocketDuplicates() { int pair[2]; Require(socketpair_nid_postfix(Unix, Stream, 0, pair) == 0); const int duplicate = dup_nid_postfix(pair[0]); - Require(duplicate >= FirstSocket && duplicate != pair[0] && duplicate != pair[1]); + Require(duplicate >= 0 && duplicate < DescriptorSetSize && duplicate != pair[0] && duplicate != pair[1]); Require(close_nid_postfix(pair[0]) == 0); RequireSocketCarries(duplicate, pair[1], "socket"); - const int target = duplicate + 0x1000; + int pipe[2]; + Require(pipe_nid_postfix(pipe) == 0); + const int target = pipe[0]; Require(dup2_nid_postfix(duplicate, target) == target); Require(close_nid_postfix(duplicate) == 0); RequireSocketCarries(target, pair[1], "moved"); Require(dup2_nid_postfix(target, target) == target); int next[2]; Require(socketpair_nid_postfix(Unix, Stream, 0, next) == 0); - Require(next[0] > target && next[1] > target); + Require(next[0] < DescriptorSetSize && next[1] < DescriptorSetSize); RequireSocketCarries(target, pair[1], "kept"); Require(dup2_nid_postfix(next[0], target) == target); RequireSocketCarries(target, next[1], "over"); Require(Throws([&] { dup2_nid_postfix(target, 1); })); - int pipe[2]; - Require(pipe_nid_postfix(pipe) == 0); - Require(Throws([&] { dup2_nid_postfix(pipe[0], target); })); - for (const int descriptor : {target, pair[1], next[0], next[1], pipe[0], pipe[1]}) Require(close_nid_postfix(descriptor) == 0); + int other[2]; + Require(pipe_nid_postfix(other) == 0); + Require(dup2_nid_postfix(other[1], target) == target); + RequirePipeCarries(target, other[0], "file"); + Require(send_nid_postfix(target, "x", 1, 0) == -1); + for (const int descriptor : {target, pair[1], next[0], next[1], pipe[1], other[0], other[1]}) Require(close_nid_postfix(descriptor) == 0); } static void RandomDeviceDuplicates() { @@ -171,7 +176,7 @@ static void RandomDeviceDuplicates() { Require(read_nid_postfix(secondRandom, randomBuf4, sizeof(randomBuf4)) == sizeof(randomBuf4)); Require(std::memcmp(randomBuf4, randomBuf3, sizeof(randomBuf4)) != 0); - const int invalidFd = FirstSocket - 100; + const int invalidFd = Unopened - 100; RequireFailure(dup2_nid_postfix(invalidFd, secondRandom), GuestEbadf); unsigned char randomBuf5[16] = {}; Require(read_nid_postfix(secondRandom, randomBuf5, sizeof(randomBuf5)) == sizeof(randomBuf5)); diff --git a/core/libs/tests/GuestFstatfs.cpp b/core/libs/tests/GuestFstatfs.cpp index cc06e5e21..0c86546cc 100644 --- a/core/libs/tests/GuestFstatfs.cpp +++ b/core/libs/tests/GuestFstatfs.cpp @@ -57,7 +57,7 @@ static constexpr std::uint32_t StatfsVersion = 0x20030518; static constexpr int GuestEbadf = 9; static constexpr int GuestEfault = 14; static constexpr int GuestEinval = 22; -static constexpr int FirstSocket = 0x10000000; +static constexpr int DescriptorSetSize = 1024; static void RequireFailure(int descriptor, int error) { GuestStatfs fs; @@ -116,7 +116,7 @@ int main() { int sockets[2]; Require(socketpair_nid_postfix(1, 1, 0, sockets) == 0); - Require(sockets[0] >= FirstSocket); + Require(sockets[0] >= 0 && sockets[0] < DescriptorSetSize); RequireFailure(sockets[0], GuestEinval); Require(close_nid_postfix(sockets[0]) == 0); Require(close_nid_postfix(sockets[1]) == 0); diff --git a/core/libs/tests/GuestPoll.cpp b/core/libs/tests/GuestPoll.cpp index 1308a06b8..446ff33dd 100644 --- a/core/libs/tests/GuestPoll.cpp +++ b/core/libs/tests/GuestPoll.cpp @@ -21,6 +21,7 @@ int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); 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 close_nid_postfix(int); +int APS5_VABI open_nid_postfix(const char*, int, int); int APS5_VABI poll_nid_postfix(PollDescriptor*, std::uint32_t, int); int APS5_VABI pipe_nid_postfix(int*); std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t); @@ -110,7 +111,7 @@ static void TestPipes(int socket) { } int main() { const int listener = socket_nid_postfix(2, 1, 0); - Require(listener >= 0); + Require(listener >= 0 && listener < 1024); std::array address{16, 2, 0, 0, 127, 0, 0, 1}; Require(bind_nid_postfix(listener, address.data(), address.size()) == 0); std::uint32_t size = address.size(); @@ -145,5 +146,16 @@ int main() { Require(poll_nid_postfix(&single, 1, -2) == -1 && *__error_nid_postfix() == 22); TestPipes(listener); Require(close_nid_postfix(server) == 0); + const int file = open_nid_postfix("/dev/urandom", 0, 0); + Require(file >= 0); + PollDescriptor mixed[2]{{listener, In, 0}, {file, In, 0}}; + bool unsupported = false; + try { + poll_nid_postfix(mixed, 2, 0); + } catch (const std::runtime_error&) { + unsupported = true; + } + Require(unsupported); + Require(close_nid_postfix(file) == 0); Require(close_nid_postfix(listener) == 0); } diff --git a/core/libs/tests/GuestPosixFilesystem.cpp b/core/libs/tests/GuestPosixFilesystem.cpp index 540ca69d5..0e3326d3d 100644 --- a/core/libs/tests/GuestPosixFilesystem.cpp +++ b/core/libs/tests/GuestPosixFilesystem.cpp @@ -1,5 +1,4 @@ #include "prx/libc/include/General.hpp" -#include "prx/libkernel/Socket/include/SocketRuntime.hpp" #include #include #include @@ -111,7 +110,7 @@ int main() { Require(close_nid_postfix(pipe) == 0); } const int socket = socket_nid_postfix(2, 2, 0); - Require(socket >= GuestSockets::FirstDescriptor); + Require(socket >= 0 && socket < 1024); Require(isatty_nid_postfix(socket) == 0 && *__error_nid_postfix() == 25); Require(close_nid_postfix(socket) == 0); Require(isatty_nid_postfix(socket) == 0 && *__error_nid_postfix() == 9); diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index 2ad9a8a60..96d7ded24 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -59,6 +59,8 @@ int APS5_VABI sceNetResolverGetError(int, int*); int APS5_VABI sceNetCtlGetState(int*); int APS5_VABI select_nid_postfix(int, void*, void*, void*, const void*); int APS5_VABI open_nid_postfix(const char*, int, int); +int APS5_VABI socket_nid_postfix(int, int, int); +int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); int APS5_VABI close_nid_postfix(int); int APS5_VABI setsockopt_nid_postfix(int, int, int, const void*, std::uint32_t); int* APS5_VABI __error_nid_postfix(); @@ -578,7 +580,7 @@ int main() { const int udp_receiver = sceNetSocket(nullptr, 2, 2, 17); const int udp_sender = sceNetSocket(nullptr, 2, 2, 17); - Require(udp_receiver >= 0 && udp_sender >= 0); + Require(udp_receiver >= 0 && udp_sender >= 0 && udp_receiver < 1024 && udp_sender < 1024); address = {16, 2, 0, 0, 127, 0, 0, 1}; Require(sceNetBind_nid_postfix(udp_receiver, address.data(), address.size()) == 0); address_size = address.size(); @@ -621,6 +623,17 @@ int main() { Require(((random_readable[udp_receiver / 64] >> (udp_receiver % 64)) & 1u) == 0); Require(random_urgent[random / 64] == 0); Require(close_nid_postfix(random) == 0); + const int kernel_socket = socket_nid_postfix(2, 2, 0); + Require(kernel_socket >= 0 && kernel_socket < 1024 && kernel_socket != udp_receiver && kernel_socket != udp_sender); + std::array kernel_address{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(kernel_socket, kernel_address.data(), kernel_address.size()) == 0); + std::uint64_t mixed_writable[16]{}; + mixed_writable[kernel_socket / 64] |= std::uint64_t{1} << (kernel_socket % 64); + mixed_writable[udp_sender / 64] |= std::uint64_t{1} << (udp_sender % 64); + Require(select_nid_postfix(std::max(kernel_socket, udp_sender) + 1, nullptr, mixed_writable, nullptr, wait_second) == 2); + Require(((mixed_writable[kernel_socket / 64] >> (kernel_socket % 64)) & 1u) != 0); + Require(((mixed_writable[udp_sender / 64] >> (udp_sender % 64)) & 1u) != 0); + Require(close_nid_postfix(kernel_socket) == 0); std::uint64_t stdio_readable[16]{1}; std::uint64_t stdio_writable[16]{6}; Require(select_nid_postfix(3, stdio_readable, stdio_writable, nullptr, poll_now) == 2); diff --git a/core/libs/tests/GuestSelect.cpp b/core/libs/tests/GuestSelect.cpp index bd6cd8d50..f5eddd3d8 100644 --- a/core/libs/tests/GuestSelect.cpp +++ b/core/libs/tests/GuestSelect.cpp @@ -25,6 +25,12 @@ extern "C" { int APS5_VABI select_nid_postfix(int, void*, void*, void*, const void*); int APS5_VABI pipe_nid_postfix(int*); int APS5_VABI close_nid_postfix(int); +int APS5_VABI socket_nid_postfix(int, int, int); +int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); +int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); +int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t); +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); std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t); std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t); int* APS5_VABI __error_nid_postfix(); @@ -106,6 +112,44 @@ int main() { Require(*__error_nid_postfix() == GuestEinval); } + { + int pipe[2]{-1, -1}; + Require(pipe_nid_postfix(pipe) == 0); + const int receiver = socket_nid_postfix(2, 2, 0); + const int sender = socket_nid_postfix(2, 2, 0); + Require(receiver >= 0 && receiver < GuestDescriptorLimit && sender >= 0 && sender < GuestDescriptorLimit); + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(receiver, address.data(), address.size()) == 0); + std::uint32_t size = address.size(); + Require(getsockname_nid_postfix(receiver, address.data(), &size) == 0); + Require(connect_nid_postfix(sender, address.data(), address.size()) == 0); + const int highest = receiver > pipe[0] ? receiver : pipe[0]; + GuestDescriptorSet idle{}; + idle.Add(receiver); + idle.Add(pipe[0]); + GuestTimeval none{0, 0}; + Require(select_nid_postfix(highest + 1, idle.words.data(), nullptr, nullptr, &none) == 0); + Require(!idle.Has(receiver) && !idle.Has(pipe[0])); + const char byte = 'm'; + Require(send_nid_postfix(sender, &byte, 1, 0) == 1); + Require(write_nid_postfix(pipe[1], &byte, 1) == 1); + GuestDescriptorSet read{}; + GuestTimeval limit{2, 0}; + const auto start = std::chrono::steady_clock::now(); + int ready = 0; + while (ready < 2 && std::chrono::steady_clock::now() - start < std::chrono::seconds(2)) { + read = {}; + read.Add(receiver); + read.Add(pipe[0]); + ready = select_nid_postfix(highest + 1, read.words.data(), nullptr, nullptr, &limit); + } + Require(ready == 2 && read.Has(receiver) && read.Has(pipe[0])); + char buffer[1]{}; + Require(recv_nid_postfix(receiver, buffer, sizeof(buffer), 0) == 1 && buffer[0] == byte); + Require(read_nid_postfix(pipe[0], buffer, sizeof(buffer)) == 1 && buffer[0] == byte); + for (const int descriptor : {receiver, sender, pipe[0], pipe[1]}) Require(close_nid_postfix(descriptor) == 0); + } + Require(close_nid_postfix(reader) == 0); Require(close_nid_postfix(writer) == 0); diff --git a/core/libs/tests/GuestSocketDescriptors.cpp b/core/libs/tests/GuestSocketDescriptors.cpp new file mode 100644 index 000000000..4598d038e --- /dev/null +++ b/core/libs/tests/GuestSocketDescriptors.cpp @@ -0,0 +1,185 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +struct GuestTimeval { + std::int64_t seconds; + std::int64_t microseconds; +}; + +struct GuestDescriptorSet { + std::array words{}; + + void Add(int descriptor) { + words[descriptor / 64] |= (std::uint64_t{1} << (descriptor % 64)); + } + + bool Has(int descriptor) const { + return ((words[descriptor / 64] >> (descriptor % 64)) & 1u) != 0; + } +}; + +struct PollDescriptor { + int descriptor; + short events; + short revents; +}; + +extern "C" { +int APS5_VABI socket_nid_postfix(int, int, int); +int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); +int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); +int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t); +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 close_nid_postfix(int); +int APS5_VABI sceKernelClose(int); +int APS5_VABI dup_nid_postfix(int); +int APS5_VABI open_nid_postfix(const char*, int, int); +std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t); +int APS5_VABI select_nid_postfix(int, void*, void*, void*, const void*); +int APS5_VABI poll_nid_postfix(PollDescriptor*, std::uint32_t, int); +int* APS5_VABI __error_nid_postfix(); +} + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "socket descriptor check failed at line %d, errno %d\n", line, *__error_nid_postfix()); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +constexpr int DescriptorSetSize = 1024; +constexpr int Inet = 2; +constexpr int Datagram = 2; +constexpr short In = 0x1; +constexpr int KernelBadDescriptor = static_cast(0x80020009u); + +template static bool Throws(TCall call) { + try { + call(); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +static int Socket() { + const int descriptor = socket_nid_postfix(Inet, Datagram, 0); + Require(descriptor >= 0 && descriptor < DescriptorSetSize); + return descriptor; +} + +static int BoundSocket(std::array& address) { + const int descriptor = Socket(); + address = {16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(descriptor, address.data(), address.size()) == 0); + std::uint32_t size = address.size(); + Require(getsockname_nid_postfix(descriptor, address.data(), &size) == 0); + return descriptor; +} + +static int ConnectedSocket(const std::array& address) { + const int descriptor = Socket(); + Require(connect_nid_postfix(descriptor, address.data(), address.size()) == 0); + return descriptor; +} + +static void WaitReadable(int descriptor) { + GuestDescriptorSet read{}; + read.Add(descriptor); + GuestTimeval limit{2, 0}; + Require(select_nid_postfix(descriptor + 1, read.words.data(), nullptr, nullptr, &limit) == 1); + Require(read.Has(descriptor)); +} + +static void SelectSocketAndFile() { + std::array address{}; + const int receiver = BoundSocket(address); + const int sender = ConnectedSocket(address); + const int file = open_nid_postfix("/dev/urandom", 0, 0); + Require(file >= 0 && file < DescriptorSetSize && file != receiver && file != sender); + const int highest = std::max({receiver, sender, file}); + { + GuestDescriptorSet read{}; + read.Add(receiver); + GuestTimeval none{0, 0}; + Require(select_nid_postfix(receiver + 1, read.words.data(), nullptr, nullptr, &none) == 0); + Require(!read.Has(receiver)); + } + { + GuestDescriptorSet read{}; + read.Add(receiver); + read.Add(file); + GuestTimeval none{0, 0}; + Require(select_nid_postfix(highest + 1, read.words.data(), nullptr, nullptr, &none) == 1); + Require(read.Has(file) && !read.Has(receiver)); + } + const char byte = 'd'; + Require(send_nid_postfix(sender, &byte, 1, 0) == 1); + WaitReadable(receiver); + { + GuestDescriptorSet read{}; + GuestDescriptorSet write{}; + read.Add(receiver); + read.Add(file); + write.Add(sender); + GuestTimeval none{0, 0}; + Require(select_nid_postfix(highest + 1, read.words.data(), write.words.data(), nullptr, &none) == 3); + Require(read.Has(receiver) && read.Has(file) && write.Has(sender)); + } + char received = 0; + Require(recv_nid_postfix(receiver, &received, 1, 0) == 1 && received == byte); + for (const int descriptor : {receiver, sender, file}) Require(close_nid_postfix(descriptor) == 0); +} + +static void CloseReleasesNumber() { + const int socket = Socket(); + Require(close_nid_postfix(socket) == 0); + std::vector files; + while (files.size() < 8 && (files.empty() || files.back() != socket)) { + const int file = open_nid_postfix("/dev/urandom", 0, 0); + Require(file >= 0); + files.push_back(file); + } + Require(files.back() == socket); + unsigned char bytes[4]{}; + Require(read_nid_postfix(socket, bytes, sizeof(bytes)) == sizeof(bytes)); + PollDescriptor entry{socket, In, 0}; + Require(Throws([&] { poll_nid_postfix(&entry, 1, 0); })); + for (const int file : files) Require(close_nid_postfix(file) == 0); + const int kernelClosed = Socket(); + Require(sceKernelClose(kernelClosed) == 0); + Require(sceKernelClose(kernelClosed) == KernelBadDescriptor); + entry = {kernelClosed, In, 0}; + Require(poll_nid_postfix(&entry, 1, 0) == 1 && entry.revents == 0x20); +} + +static void DuplicateKeepsSocket() { + std::array address{}; + const int receiver = BoundSocket(address); + const int sender = ConnectedSocket(address); + const int duplicate = dup_nid_postfix(receiver); + Require(duplicate >= 0 && duplicate < DescriptorSetSize && duplicate != receiver && duplicate != sender); + Require(close_nid_postfix(receiver) == 0); + const char byte = 'u'; + Require(send_nid_postfix(sender, &byte, 1, 0) == 1); + WaitReadable(duplicate); + char received = 0; + Require(recv_nid_postfix(duplicate, &received, 1, 0) == 1 && received == byte); + Require(close_nid_postfix(duplicate) == 0); + Require(close_nid_postfix(sender) == 0); +} + +int main() { + SelectSocketAndFile(); + CloseReleasesNumber(); + DuplicateKeepsSocket(); + return 0; +} diff --git a/core/libs/tests/GuestSocketPair.cpp b/core/libs/tests/GuestSocketPair.cpp index 561645111..3ab148efd 100644 --- a/core/libs/tests/GuestSocketPair.cpp +++ b/core/libs/tests/GuestSocketPair.cpp @@ -51,7 +51,7 @@ constexpr int CloseOnExec = 0x10000000; constexpr int NonBlocking = 0x20000000; constexpr int NoSignal = 0x20000; constexpr int Peek = 2; -constexpr int FirstSocket = 0x10000000; +constexpr int DescriptorSetSize = 1024; constexpr short In = 0x1; constexpr short Out = 0x4; constexpr short Hup = 0x10; @@ -90,7 +90,7 @@ static bool Unnamed(int descriptor, int (APS5_VABI *query)(int, void*, std::uint int main() { int pair[2] = {-1, -1}; Require(socketpair_nid_postfix(Unix, Stream, 0, pair) == 0); - Require(pair[0] >= FirstSocket && pair[1] >= FirstSocket && pair[0] != pair[1]); + Require(pair[0] >= 0 && pair[0] < DescriptorSetSize && pair[1] >= 0 && pair[1] < DescriptorSetSize && pair[0] != pair[1]); Require(Exchange(pair[0], pair[1], "one way")); Require(Exchange(pair[1], pair[0], "other way")); Require(Unnamed(pair[0], getsockname_nid_postfix) && Unnamed(pair[1], getpeername_nid_postfix)); diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 2e5574be7..5451d80af 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -57,7 +57,7 @@ - [Map hints inside a reservation](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) (libkernel) - `sceKernelMapDirectMemory` and the flexible and anonymous maps place a mapping without `MAP_FIXED` at its hint address when the whole range lies in a range still reserved by `sceKernelReserveVirtualRange`, as if `MAP_FIXED` were set. Taken from PPSA01474, which reserves 64 GiB and then maps its heaps inside it with hints and no `MAP_FIXED`, and uses the hint addresses afterwards; not measured on the console. Hints outside a reservation, or over a range that is partly mapped, stay hints - [_sceKernelRtldThreadAtexitIncrement, _sceKernelRtldThreadAtexitDecrement](../../../core/libs/prx/libkernel/System/src/Rtld.cpp) (libkernel) - the argument is the `dso` address a title's `libc.prx` passes to `__cxa_thread_atexit` (PPSA14632), which fails when Increment returns nonzero. As in the [fpPS4](https://github.com/red-prig/fpPS4) reimplementation, Increment takes a reference on the loaded image containing the address and Decrement releases one; the variants without the leading underscore are assumed to match - [tcgetattr, tcsetattr](../../../core/libs/prx/libkernel/File/src/Terminal.cpp) (libkernel) - every file and pipe descriptor is taken as not a terminal (`ENOTTY`), and a socket gets `EADDRNOTAVAIL` for `AF_INET` and `EOPNOTSUPP` otherwise, as FreeBSD's `in_control` and `pru_control_notsupp` answer a termios ioctl; what descriptors 0 to 2 of a title are on the console, and whether any of them is a terminal there, is unknown -- [select](../../../core/libs/prx/libkernel/Socket/src/Socket.cpp) (libkernel) - descriptor 0 is never reported readable and descriptors 1 and 2 are always writable, as for a console title with no input; reads from descriptor 0 still go to the host stdin. `/dev/random` and `/dev/urandom` are always readable and writable, as FreeBSD reports a seeded random device. Other descriptors that are not libSceNet sockets throw. Dungeons (PPSA16064) selects on descriptor 0 +- [select](../../../core/libs/prx/libkernel/Socket/src/Socket.cpp) (libkernel) - descriptor 0 is never reported readable and descriptors 1 and 2 are always writable, as for a console title with no input; reads from descriptor 0 still go to the host stdin. `/dev/random` and `/dev/urandom` are always readable and writable, as FreeBSD reports a seeded random device. Guest sockets and libSceNet sockets are polled; other descriptors throw on Windows and are polled through the host on Linux. Dungeons (PPSA16064) selects on descriptor 0 - [select](../../../core/libs/prx/libkernel/Socket/src/Socket.cpp) (libkernel) - a descriptor the socket poller reports as unknown that is below `GuestSockets::FirstDescriptor` is polled on the host instead of throwing, except on Windows where it still throws; an invalid host descriptor fails with `EBADF`, and mixed host and socket sets are polled in short alternating slices so neither side waits out the full slice alone. Wobbly Life (PPSA29413) selects on file descriptors while streaming its bundles - [__progname](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - unknown data export - [environ](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - data export pointing to a fixed empty environment so titles that import it load; it is not the environment libc's `getenv` reads, so it lists none of those variables and `setenv`/`putenv` changes do not appear in it @@ -128,7 +128,7 @@ - [Thread cancellation](../../../core/libs/prx/libkernel/Pthread/src/Thread.cpp) (libkernel) follows FreeBSD libthr (states and types 0/1 and 0/2, `PTHREAD_CANCELED` 1, the `_pthread_cleanup_info` node layout), not measured on the console. A request is acted on only in `scePthreadTestcancel`, the condition variable, `sem` and `scePthreadSem` waits, `scePthreadJoin` and the sleeps, and their POSIX names; file and socket calls are not cancellation points. A sleep is not interrupted: the thread exits when it ends. The asynchronous type acts like the deferred one, except at the two set calls. - [cpuset_setaffinity](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - only the calling thread or the calling process is handled. Bits outside `sceKernelGetAvailableCpumask` are dropped, and a mask with none of those bits fails with `EDEADLK`, as FreeBSD does when the new set has no CPU in common with the parent set - [cpuset_getaffinity](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - only the calling thread or the calling process is handled. For the process the mask is the union of the affinities of the live guest threads, as [FreeBSD 11.4](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/sys/kern/kern_cpuset.c#L1150-L1156) ORs the mask of every thread in the process; threads the guest did not create through libkernel are not included, and the console result is unverified -- [dup and dup2](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) throw for the combinations they do not model: `dup2` between a guest socket and a host descriptor in either direction. Guest sockets share one socket object between the duplicates and the descriptor numbers come from the guest socket counter, not the lowest free number as in FreeBSD `kern_dup`. The errno values for descriptor exhaustion are not verified against the console. +- [dup and dup2](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) throw for the combinations they do not model: `dup2` of a guest socket onto descriptors 0 to 2, whose host streams libc writes without the socket table, and on Windows duplicating a directory descriptor. Guest sockets share one socket object between the duplicates; the numbers come from the host descriptor table like those of files. The errno values for descriptor exhaustion are not verified against the console. - [waitpid](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) returns -1 with `ECHILD` (`EINVAL` for unknown options): nothing here creates a child process; child processes would need a real `waitpid`. - [execv](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) throws: replacing the process image is not supported, the same as `execvp`. PPSA97358 imports it and fails to load without it. - [kern.proc.pathname](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) returns `/app0/eboot.bin` for pid -1 and the calling process, following FreeBSD's `sysctl_kern_proc_pathname`; the exact pathname on retail consoles remains unverified. @@ -139,9 +139,9 @@ - [socketpair](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) - the pair is the host's `AF_UNIX` pair on Linux and, on Windows, a loopback TCP connection for `SOCK_STREAM` or two connected UDP sockets for `SOCK_DGRAM`; `SOCK_SEQPACKET` throws on Windows. A datagram sent to a full receiver gets `ENOBUFS` on the console, waits or gets `EAGAIN` on Linux and is dropped on Windows, and on Windows a datagram `recv` after the peer closed reports `ECONNRESET` instead of the console's `ENOTCONN`, and a stream `send` after the peer closed reports `ECONNABORTED` instead of the console's `EPIPE`. `read` and `write` on a pair end go to the host like on every guest socket. `getpeername` on a pair end succeeds after the peer closed, where the console returns `ENOTCONN`, and `connect`, `bind` and `sendto` with an address on a pair end report an address-family error instead of the console's `EISCONN`. On Windows `poll` on a stream end reports `POLLHUP` without `POLLIN` after the peer's `shutdown(SHUT_WR)` or `close`, where the console reports `POLLIN`, with `POLLHUP` only after `close` - [UDP recv/recvfrom](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) and [sceNetRecv/sceNetRecvfrom](../../../core/libs/prx/libSceNet/Export.cpp) translate Windows WSAEMSGSIZE into the copied byte count for datagram sockets, including MSG_PEEK, and retain the sender address. This follows FreeBSD 11.4 recv(2) and Winsock's documented truncated-datagram behavior; console behavior and existing title impact are unverified. - [send, sendto](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) - send and sendto without an address on an unconnected datagram socket map Windows `WSAENOTCONN` to FreeBSD's `EDESTADDRREQ` (39). -- [read, write and vector I/O](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) on a guest socket use `recv` and `send` without flags; `sceKernelReadv` and `sceKernelWritev` gather or scatter through one socket operation, preserving stream short reads and datagram boundaries. The positioned `pread`, `pwrite`, `preadv` and `pwritev` forms, and their `sceKernel` forms, return `ESPIPE` for an open guest socket and `EBADF` for a closed one. Guest socket descriptors start at `0x10000000` and are allocated upward, so the descriptor range selects the guest socket table or the host descriptor table. `EPIPE` from a write raises the host's `SIGPIPE` on Linux, which the guest can neither ignore nor suppress: `signal(SIGPIPE, SIG_IGN)` and `sigaction` record the disposition, as they do for every number from 1 to 128, but only the six signals with host equivalents (2, 4, 6, 8, 11 and 15) take effect on the host or are delivered, so the host `SIGPIPE` is not stopped; `SO_NOSIGPIPE` is not an option here, and raises nothing on Windows; a `write` longer than `INT_MAX` bytes is `EMSGSIZE` where the console sends it. AIO on a guest socket still goes to the host, which has no such descriptor -- [poll](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) waits on guest sockets and pipes, through `WSAPoll` on Windows and `poll` on Linux; the `pollfd` layout and flag values are FreeBSD's. A pipe follows FreeBSD's `pipe_poll`: the read end is readable when data is buffered, either end reports `POLLHUP` once the other end is closed, with the requested `POLLIN` and `POLLRDNORM` bits and without `POLLOUT`, which `pollscan` clears, and the write end is writable when `PIPE_BUF` (512) bytes are free on Windows, or when the host kernel says so on Linux. `POLLOUT` on a read end throws: FreeBSD opens both ends for reading and writing and reports it writable, and host pipes are one-way. Windows pipes cannot be waited on, so while a pipe is polled `poll` checks it with `NtQueryInformationFile` and waits on the sockets in slices of 1 ms. A closed descriptor below the socket range reports `POLLNVAL`, and any other descriptor there (files, stdio) throws, as do event bits other than `POLLIN`, `POLLPRI`, `POLLOUT`, `POLLRDNORM`, `POLLRDBAND` and `POLLWRBAND` (the `POLLERR`, `POLLHUP` and `POLLNVAL` bits are ignored in `events`), `POLLPRI` on Windows, which `WSAPoll` rejects, and an infinite timeout with no socket or pipe to wait on. A closed socket descriptor reports `POLLNVAL`; the console's limit on the descriptor count is unknown and not checked -- [Socket error numbers](../../../core/libs/prx/libkernel/Socket/include/SocketError.hpp) (libkernel) - a host socket error becomes the FreeBSD 11 `errno.h` number of the same name; the numbers are not checked on the console. Winsock `WSAESHUTDOWN` becomes `EPIPE`, as a send after `shutdown` does on FreeBSD, and a host error with no FreeBSD socket counterpart becomes `EIO`. A nonblocking `connect` in progress reports `EINPROGRESS` on Linux but `EAGAIN` on Windows (`WSAEWOULDBLOCK`), where FreeBSD reports `EINPROGRESS`. On Windows a refused nonblocking `connect` is never reported (`SO_ERROR` is unsupported), so a repeated one keeps reporting `EALREADY` where FreeBSD reports `ECONNREFUSED`. A receive after `shutdown(SHUT_RD)` reports `EPIPE` on Windows where FreeBSD returns 0 +- [read, write and vector I/O](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) on a guest socket use `recv` and `send` without flags; `sceKernelReadv` and `sceKernelWritev` gather or scatter through one socket operation, preserving stream short reads and datagram boundaries. The positioned `pread`, `pwrite`, `preadv` and `pwritev` forms, and their `sceKernel` forms, return `ESPIPE` for an open guest socket and `EBADF` for a closed one. `EPIPE` from a write raises the host's `SIGPIPE` on Linux, which the guest can neither ignore nor suppress: `signal(SIGPIPE, SIG_IGN)` and `sigaction` record the disposition, as they do for every number from 1 to 128, but only the six signals with host equivalents (2, 4, 6, 8, 11 and 15) take effect on the host or are delivered, so the host `SIGPIPE` is not stopped; `SO_NOSIGPIPE` is not an option here, and raises nothing on Windows; a `write` longer than `INT_MAX` bytes is `EMSGSIZE` where the console sends it. AIO on a guest socket still goes to the host, which has no such descriptor +- [Socket descriptor numbers](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel, libSceNet) - `socket`, `accept`, `socketpair`, `dup`, `sceNetSocket` and `sceNetAccept` take the lowest free number of the host descriptor table that files use, as FreeBSD 11.4 `fdalloc` takes the lowest free entry of the one process table (`kern_descrip.c`), so every number stays below `FD_SETSIZE` (1024) while fewer than 1024 descriptors are open. The number holds a zero-access `NUL` handle on Windows and a duplicate of the host socket on Linux, and the socket tables map it to the socket. The host table also holds the runtime's own files, so the numbers differ from the console's; the first number `socket` and `sceNetSocket` return on a console and its `getdtablesize` are not measured. Paths that bypass the socket tables see the placeholder: libc `fdopen` on a socket opens it, libkernel `fstat` on a socket throws, and libkernel `close` of a libSceNet socket releases the number but leaves the libSceNet socket open. Palworld (PPSA23126), The Mound (PPSA26223) and Dying Light 2 (PPSA02262) set socket bits in a 1024-bit `fd_set` +- [poll](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) waits on guest sockets and pipes, through `WSAPoll` on Windows and `poll` on Linux; the `pollfd` layout and flag values are FreeBSD's. An open descriptor that is neither a guest socket nor a pipe (files, stdio, libSceNet sockets) throws, as do event bits other than `POLLIN`, `POLLPRI`, `POLLOUT`, `POLLRDNORM`, `POLLRDBAND` and `POLLWRBAND` (the `POLLERR`, `POLLHUP` and `POLLNVAL` bits are ignored in `events`), `POLLPRI` on Windows, which `WSAPoll` rejects, and an infinite timeout with no socket to wait on. A closed socket descriptor reports `POLLNVAL`; the console's limit on the descriptor count is unknown and not checked- [Socket error numbers](../../../core/libs/prx/libkernel/Socket/include/SocketError.hpp) (libkernel) - a host socket error becomes the FreeBSD 11 `errno.h` number of the same name; the numbers are not checked on the console. Winsock `WSAESHUTDOWN` becomes `EPIPE`, as a send after `shutdown` does on FreeBSD, and a host error with no FreeBSD socket counterpart becomes `EIO`. A nonblocking `connect` in progress reports `EINPROGRESS` on Linux but `EAGAIN` on Windows (`WSAEWOULDBLOCK`), where FreeBSD reports `EINPROGRESS`. On Windows a refused nonblocking `connect` is never reported (`SO_ERROR` is unsupported), so a repeated one keeps reporting `EALREADY` where FreeBSD reports `ECONNREFUSED`. A receive after `shutdown(SHUT_RD)` reports `EPIPE` on Windows where FreeBSD returns 0 - [connect to AF_UNSPEC](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) dissolves a datagram association as FreeBSD 11.4's `udp_disconnect()` does, which resets the local address to the wildcard address and keeps the port. On Linux a socket bound to an explicit port keeps its local address as well (`127.0.0.1:` stays `127.0.0.1:`), because a bound socket cannot be bound again; one bound to port 0 is bound again to the wildcard address with the port it had - [msync](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) follows FreeBSD 11's `kern_msync` and `vm_map_sync`, not measured on the console: the address is rounded down to the page, a range that wraps is `EINVAL`, as is `MS_ASYNC` with `MS_INVALIDATE`, and a range whose start or any page is not mapped (as `sceKernelVirtualQuery` reports it, reservations included) is `ENOMEM`; a page unmapped from inside a larger mapping still counts as mapped there, since `sceKernelVirtualQuery` keeps reporting the whole mapping. The guest has no file mappings, so a successful call writes nothing back. `MS_INVALIDATE` on a mapped range throws: FreeBSD returns `EBUSY` when the range holds `mlock`ed pages, and locked pages are not tracked - [sigtimedwait](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) waits on a process-wide pending set instead of throwing: a signal raised while blocked stays pending and is taken by `sigwait`, `sigwaitinfo` or `sigtimedwait`, delivered to the handler when `_sigprocmask` unblocks it, or delivered by `pause`. The set covers all 128 signals and the lowest number that is pending and in the set is selected (FreeBSD specifies the lowest number only among real-time signals). Selection, `EAGAIN` on timeout, `EINVAL` for a `tv_nsec` outside 0 to 1000000000 while nothing is pending and `NULL` timeout blocking indefinitely follow FreeBSD's [sigtimedwait(2)](https://man.freebsd.org/cgi/man.cgi?query=sigtimedwait). `sigwait` returns 0 with the number in its output, as POSIX specifies, and `sigwaitinfo` and `sigtimedwait` return the number, as FreeBSD does. The guest `siginfo_t` follows FreeBSD 11's `sys/sys/siginfo.h`: 80 bytes, `si_signo` at 0, `si_code` at 8, `si_pid` at 12, `si_uid` at 16, `si_addr` at 24, `si_value` at 32 and `si_band` at 40; only `si_signo`, `si_code` (`SI_USER`) and `si_pid` (the guest pid) are written, `si_uid` stays 0 because no uid is modelled, and the console's layout is not verified. PPSA85300 imports `sigtimedwait` and fails to load without it; its only call site is VLC's `vlc_writev` ([filesystem.c](https://github.com/videolan/vlc/blob/6de05adcbaf2e8b85fe86aad4169393098628119/src/posix/filesystem.c#L243-L278)), which polls with a zero timeout to drain a `SIGPIPE` only after `writev` fails with `EPIPE`. The waits are not cancellation points and an unblocked caught signal does not interrupt them with `EINTR`, where FreeBSD's `sigwaitinfo` and `sigtimedwait` may return it. `SIGPIPE` has no host equivalent and is never raised by a host write here (see `read, write and vector I/O`), so VLC's drain finds nothing From 488e18691a475b7ec731761fb1e3b6025e5379ab Mon Sep 17 00:00:00 2001 From: MarcZX100 Date: Mon, 12 Oct 2026 08:46:15 +0000 Subject: [PATCH 2/5] fix(libkernel): route socket options by table membership --- core/libs/prx/libkernel/Socket/src/Udp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index c611762e1..dad367314 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -720,7 +720,7 @@ int APS5_VABI fcntl_nid_postfix(int descriptor, int command, ...) { int APS5_VABI setsockopt_nid_postfix(int descriptor, int level, int option, const void* value, std::uint32_t length) { int netError = 0; - if (descriptor < GuestSockets::FirstDescriptor && KernelSetSocketOption_nid_no_patch(descriptor, level, option, value, length, &netError)) { + if (!GuestSockets::IsOpen(descriptor) && KernelSetSocketOption_nid_no_patch(descriptor, level, option, value, length, &netError)) { return netError == 0 ? 0 : Fail(netError); } const auto socket = Lookup(descriptor); From c33036eefea0bcb4435dc1a2ff7125a0fd2349e1 Mon Sep 17 00:00:00 2001 From: MarcZX100 Date: Mon, 12 Oct 2026 08:50:39 +0000 Subject: [PATCH 3/5] fix(libkernel): return bad descriptor from fstat --- core/libs/prx/libkernel/File/src/Stdio.cpp | 2 +- core/libs/tests/GuestSocketDescriptors.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index e7b833dd1..dbb451f04 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -645,7 +645,7 @@ int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int d, FileStat* sb) { APS5_INVALID_ARG_EX; } if (GuestSockets::IsOpen(d)) throw std::runtime_error(std::string(__func__) + ": the status of a guest socket is not implemented"); - File::FillFileStat(d, sb); + if (!File::FillFileStatFromDescriptor(d, sb)) return PosixFailure(SceErrorFromErrno(errno) & 0xffff); return 0; } diff --git a/core/libs/tests/GuestSocketDescriptors.cpp b/core/libs/tests/GuestSocketDescriptors.cpp index 4598d038e..e8340085a 100644 --- a/core/libs/tests/GuestSocketDescriptors.cpp +++ b/core/libs/tests/GuestSocketDescriptors.cpp @@ -1,3 +1,4 @@ +#include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include @@ -39,6 +40,7 @@ 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 close_nid_postfix(int); int APS5_VABI sceKernelClose(int); +std::int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int, FileStat*); int APS5_VABI dup_nid_postfix(int); int APS5_VABI open_nid_postfix(const char*, int, int); std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t); @@ -142,6 +144,8 @@ static void SelectSocketAndFile() { static void CloseReleasesNumber() { const int socket = Socket(); Require(close_nid_postfix(socket) == 0); + FileStat status{}; + Require(fstat_nid_disambig1_nid_postfix(socket, &status) == -1 && *__error_nid_postfix() == 9); std::vector files; while (files.size() < 8 && (files.empty() || files.back() != socket)) { const int file = open_nid_postfix("/dev/urandom", 0, 0); From eb2b360f0b4e47b2639d1da3f2cf7ccf4c02effb Mon Sep 17 00:00:00 2001 From: MarcZX100 Date: Mon, 12 Oct 2026 16:04:20 +0000 Subject: [PATCH 4/5] fix(libkernel): route updated socket descriptors by table --- core/libs/prx/libkernel/File/src/Stdio.cpp | 2 -- core/libs/prx/libkernel/Socket/src/Udp.cpp | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index dbb451f04..0a1cb021d 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -472,8 +472,6 @@ int File::DuplicateFrom(int fd, int start) { #else const int duplicate = ::fcntl(fd, F_DUPFD, start); if (duplicate < 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff); - if (duplicate >= GuestSockets::FirstDescriptor) - throw std::runtime_error("fcntl: host descriptor reached the guest socket range"); if (File::IsRandomDevice(fd)) File::RememberRandomDevice(duplicate); File::ShareDescriptor(fd, duplicate); return duplicate; diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index dad367314..a1ef0378c 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -670,7 +670,7 @@ int GuestSockets::Poll(KernelSocketPoll::Entry* entries, int count, int timeoutM extern "C" { int APS5_VABI fcntl_nid_postfix(int descriptor, int command, ...) { - if (descriptor < GuestSockets::FirstDescriptor) { + if (!GuestSockets::IsOpen(descriptor)) { const auto type = File::FcntlArgumentOf(command); std::intptr_t argument = 0; if (type != File::FcntlArgument::None) { @@ -917,7 +917,7 @@ int APS5_VABI getpeername_nid_postfix(int descriptor, void* address, std::uint32 return 0; } int APS5_VABI ioctl_nid_postfix(int descriptor, std::uint64_t request, void* argument) { - if (descriptor < GuestSockets::FirstDescriptor) return File::Ioctl(descriptor, request, argument); + if (!GuestSockets::IsOpen(descriptor)) return File::Ioctl(descriptor, request, argument); const auto socket = Lookup(descriptor); if (!socket) return -1; if (!argument) return Fail(14); From e0ec0c80948ef9c5836c2418d282dcf425399857 Mon Sep 17 00:00:00 2001 From: MarcZX100 Date: Tue, 13 Oct 2026 03:28:27 +0000 Subject: [PATCH 5/5] fix(libkernel): route equeue socket checks by membership --- core/libs/prx/libkernel/Equeue/src/FileEvent.cpp | 6 +++--- core/libs/prx/libkernel/Equeue/src/Kqueue.cpp | 10 +++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/core/libs/prx/libkernel/Equeue/src/FileEvent.cpp b/core/libs/prx/libkernel/Equeue/src/FileEvent.cpp index eee2a8077..4540fa0c3 100644 --- a/core/libs/prx/libkernel/Equeue/src/FileEvent.cpp +++ b/core/libs/prx/libkernel/Equeue/src/FileEvent.cpp @@ -559,8 +559,8 @@ int VnodeDescriptorError(int descriptor) { if (descriptor < 0) { return GUEST_EBADF; } - if (descriptor >= GuestSockets::FirstDescriptor) { - return GuestSockets::IsOpen(descriptor) ? GUEST_EINVAL : GUEST_EBADF; + if (GuestSockets::IsOpen(descriptor)) { + return GUEST_EINVAL; } if (!DescriptorIsOpen_nid_no_patch(descriptor)) { return GUEST_EBADF; @@ -647,7 +647,7 @@ int APS5_VABI sceKernelDeleteFileEvent(KernelEqueue eq, int fd) { if (!EqueuePin_nid_postfix(eq)) { return SCE_KERNEL_ERROR_EBADF; } - if (fd < 0 || (fd >= GuestSockets::FirstDescriptor ? !GuestSockets::IsOpen(fd) : !DescriptorIsOpen_nid_no_patch(fd))) { + if (fd < 0 || (!GuestSockets::IsOpen(fd) && !DescriptorIsOpen_nid_no_patch(fd))) { return SCE_KERNEL_ERROR_EBADF; } return EqueueDeleteEvent_nid_postfix(eq, static_cast(fd), EVFILT_VNODE); diff --git a/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp b/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp index a60b3dbb0..169665066 100644 --- a/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp +++ b/core/libs/prx/libkernel/Equeue/src/Kqueue.cpp @@ -118,8 +118,8 @@ int DescriptorFilterError(int kqueueDescriptor, std::uintptr_t ident) { return GUEST_EBADF; } const int descriptor = static_cast(ident); - if (descriptor >= GuestSockets::FirstDescriptor) { - return GuestSockets::IsOpen(descriptor) ? 0 : GUEST_EBADF; + if (GuestSockets::IsOpen(descriptor)) { + return 0; } if (!DescriptorIsOpen_nid_no_patch(descriptor)) { return GUEST_EBADF; @@ -135,7 +135,7 @@ int AddDescriptorKnote(const KernelEqueueRef& queue, KernelEvent& change, std::u } NotImplemented_nid_no_patch("kevent: EVFILT_READ on a kqueue descriptor"); } - if (descriptor < GuestSockets::FirstDescriptor) { + if (!GuestSockets::IsOpen(descriptor)) { 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) { @@ -325,10 +325,6 @@ int APS5_VABI kqueue_nid_postfix() { 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);