diff --git a/core/libs/prx/libkernel/Socket/src/Socket.cpp b/core/libs/prx/libkernel/Socket/src/Socket.cpp index 95da4bf78..f7e393e53 100644 --- a/core/libs/prx/libkernel/Socket/src/Socket.cpp +++ b/core/libs/prx/libkernel/Socket/src/Socket.cpp @@ -2,6 +2,7 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/File/include/RandomDevice.hpp" #include "prx/libkernel/Socket/include/SocketPoll.hpp" #include "prx/libkernel/Socket/include/SocketSelectDeadline.hpp" @@ -9,6 +10,7 @@ #include #include #include +#include #include namespace { @@ -27,6 +29,11 @@ bool DescriptorSet(const void* set, int descriptor) { return set != nullptr && ((static_cast(set)[descriptor / 64] >> (descriptor % 64)) & 1u) != 0; } +void UnknownDescriptor(int descriptor, bool polled) { + const auto what = "select on descriptor " + std::to_string(descriptor) + (polled ? " (not a libSceNet socket)" : " (libSceNet is not loaded)"); + NotImplemented_nid_no_patch(what.c_str()); +} + void ClearDescriptor(void* set, int descriptor) { if (set != nullptr) static_cast(set)[descriptor / 64] &= ~(std::uint64_t{1} << (descriptor % 64)); } @@ -59,13 +66,20 @@ int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* return -1; } std::vector entries; + std::vector devices; for (int descriptor = 0; descriptor < nfds; ++descriptor) { short events = 0; if (DescriptorSet(readfds, descriptor)) events |= KernelSocketPoll::Readable; if (DescriptorSet(writefds, descriptor)) events |= KernelSocketPoll::Writable; if (DescriptorSet(exceptfds, descriptor)) events |= KernelSocketPoll::Urgent; - if (events != 0) entries.push_back({descriptor, events, 0}); + if (events == 0) continue; + 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 entries.push_back({descriptor, events, 0}); } + const auto sockets = entries.size(); + const bool devicesReady = std::any_of(devices.begin(), devices.end(), [](const auto& entry) { return entry.revents != 0; }); + entries.insert(entries.end(), devices.begin(), devices.end()); const auto start = std::chrono::steady_clock::now(); const auto deadline = limit == nullptr ? std::chrono::steady_clock::time_point::max() : KernelSocketSelect::DeadlineAfter(start, limit->seconds, limit->microseconds); @@ -76,34 +90,35 @@ int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* const auto roundedMilliseconds = remaining > 0 ? remaining / 1000 + (remaining % 1000 != 0) : 0; waitMilliseconds = static_cast(std::clamp(roundedMilliseconds, 0, 100)); } - if (!entries.empty()) { + if (devicesReady) waitMilliseconds = 0; + if (sockets != 0) { const auto poller = g_socketPoller.load(); - if (poller == nullptr) NotImplemented_nid_no_patch("select on descriptors that are not libSceNet sockets"); - for (auto& entry : entries) entry.revents = 0; - const int result = poller(entries.data(), static_cast(entries.size()), waitMilliseconds); + if (poller == nullptr) UnknownDescriptor(entries.front().descriptor, false); + for (std::size_t index = 0; index < sockets; ++index) entries[index].revents = 0; + const int result = poller(entries.data(), static_cast(sockets), waitMilliseconds); if (result < 0) { *__error_nid_postfix() = -result; return -1; } - int ready = 0; - for (const auto& entry : entries) { - if ((entry.revents & KernelSocketPoll::Unknown) != 0) NotImplemented_nid_no_patch("select on descriptors that are not libSceNet sockets"); - const bool readable = (entry.events & KernelSocketPoll::Readable) != 0 && (entry.revents & (KernelSocketPoll::Readable | KernelSocketPoll::HangUp | KernelSocketPoll::Error)) != 0; - const bool writable = (entry.events & KernelSocketPoll::Writable) != 0 && (entry.revents & (KernelSocketPoll::Writable | KernelSocketPoll::Error)) != 0; - const bool urgent = (entry.events & KernelSocketPoll::Urgent) != 0 && (entry.revents & KernelSocketPoll::Urgent) != 0; - ready += static_cast(readable) + static_cast(writable) + static_cast(urgent); - } - if (ready > 0) { - for (const auto& entry : entries) { - if ((entry.revents & (KernelSocketPoll::Readable | KernelSocketPoll::HangUp | KernelSocketPoll::Error)) == 0) ClearDescriptor(readfds, entry.descriptor); - if ((entry.revents & (KernelSocketPoll::Writable | KernelSocketPoll::Error)) == 0) ClearDescriptor(writefds, entry.descriptor); - if ((entry.revents & KernelSocketPoll::Urgent) == 0) ClearDescriptor(exceptfds, entry.descriptor); - } - return ready; - } } else if (waitMilliseconds > 0) { std::this_thread::sleep_for(std::chrono::milliseconds(waitMilliseconds)); } + int ready = 0; + for (const auto& entry : entries) { + if ((entry.revents & KernelSocketPoll::Unknown) != 0) UnknownDescriptor(entry.descriptor, true); + const bool readable = (entry.events & KernelSocketPoll::Readable) != 0 && (entry.revents & (KernelSocketPoll::Readable | KernelSocketPoll::HangUp | KernelSocketPoll::Error)) != 0; + const bool writable = (entry.events & KernelSocketPoll::Writable) != 0 && (entry.revents & (KernelSocketPoll::Writable | KernelSocketPoll::Error)) != 0; + const bool urgent = (entry.events & KernelSocketPoll::Urgent) != 0 && (entry.revents & KernelSocketPoll::Urgent) != 0; + ready += static_cast(readable) + static_cast(writable) + static_cast(urgent); + } + if (ready > 0) { + for (const auto& entry : entries) { + if ((entry.revents & (KernelSocketPoll::Readable | KernelSocketPoll::HangUp | KernelSocketPoll::Error)) == 0) ClearDescriptor(readfds, entry.descriptor); + if ((entry.revents & (KernelSocketPoll::Writable | KernelSocketPoll::Error)) == 0) ClearDescriptor(writefds, entry.descriptor); + if ((entry.revents & KernelSocketPoll::Urgent) == 0) ClearDescriptor(exceptfds, entry.descriptor); + } + return ready; + } if (limit != nullptr && std::chrono::steady_clock::now() >= deadline) { for (const auto& entry : entries) { ClearDescriptor(readfds, entry.descriptor); diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index c4b8c8004..e71c5d65c 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -53,6 +53,8 @@ int APS5_VABI sceNetResolverDestroy(int); 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 close_nid_postfix(int); int* APS5_VABI __error_nid_postfix(); int APS5_VABI sceNetInetPton(int, const char*, void*); const char* APS5_VABI sceNetInetNtop(int, const void*, char*, std::uint32_t); @@ -422,6 +424,24 @@ int main() { const std::int64_t bad_timeout[2]{0, 1000000}; Require(select_nid_postfix(udp_receiver + 1, readable, nullptr, nullptr, bad_timeout) == -1 && *__error_nid_postfix() == 22); Require(select_nid_postfix(1025, nullptr, nullptr, nullptr, poll_now) == -1 && *__error_nid_postfix() == 22); + const int random = open_nid_postfix("/dev/urandom", 0, 0); + Require(random >= 0 && random < 1024); + std::uint64_t random_readable[16]{}; + std::uint64_t random_urgent[16]{}; + random_readable[random / 64] |= std::uint64_t{1} << (random % 64); + random_readable[udp_receiver / 64] |= std::uint64_t{1} << (udp_receiver % 64); + random_urgent[random / 64] |= std::uint64_t{1} << (random % 64); + Require(select_nid_postfix(std::max(random, udp_receiver) + 1, random_readable, nullptr, random_urgent, nullptr) == 1); + Require(((random_readable[random / 64] >> (random % 64)) & 1u) != 0); + Require(((random_readable[udp_receiver / 64] >> (udp_receiver % 64)) & 1u) == 0); + Require(random_urgent[random / 64] == 0); + Require(close_nid_postfix(random) == 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); + Require(stdio_readable[0] == 0 && stdio_writable[0] == 6); + stdio_readable[0] = 1; + Require(select_nid_postfix(1, stdio_readable, nullptr, nullptr, poll_now) == 0 && stdio_readable[0] == 0); CheckMessages(udp_receiver, udp_sender, address); Require(sceNetSocketClose(udp_sender) == 0); Require(sceNetSocketClose(udp_receiver) == 0); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 54b197157..0b1b147cb 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -194,6 +194,7 @@ Throughout the project, every function at every stage either **does exactly what - [Dinkumware mutexes and conditions](../../core/libs/prx/libc/src/Threads.cpp) (libc) use pointer-to-handle arguments, matching the console aerolib signatures reported in [#3079](https://github.com/boykopovar/AnyPS5/pull/3079). The title's `_Mtx_lock` maps `0` to `_Thrd_success` (0), `0x8002000b` to `_Thrd_busy` (3), and other failures to `_Thrd_error` (4); its internal mutex init recognizes recursive flag `0x100`, while every other type uses default attributes. The observed relock failure for a plain mutex suggests those defaults are error-checking, but that has not been confirmed on hardware. The title's `_Mtx_unlock` and `_Cnd_wait` return success regardless of their internal result. The host implementation treats invalid ownership preconditions as no-ops, and throws for invalid handles, host wait errors and destroying a locked mutex as debug guards; those cases are not confirmed against the console. - [random, srandom, initstate, setstate](../../core/libs/prx/libc/src/Random.cpp) (libc) follow FreeBSD 11 [random.c](https://github.com/freebsd/freebsd-src/blob/4ab2e064d7950be84256d671a7ae93f87cc6aa36/lib/libc/stdlib/random.c): the additive feedback generator on a default 128-byte `TYPE_3` table, `good_rand` seeding, state sizes rounded down to 8, 32, 64, 128 or 256 bytes, and `setstate` returning the previous state or null for a rear position past the degree. `initstate` below 8 bytes returns null without setting errno, as that file and its manual do (glibc sets `EINVAL`). FreeBSD does not lock the generator; here every call is serialised by one lock, so concurrent callers draw the serial sequence between them. A null state throws, where FreeBSD faults. That the console's `libSceLibcInternal` uses this FreeBSD code and not another generator is unverified - [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 - [__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 - [seteuid, setegid](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - the guest process is assumed to run as root, with real, effective and saved user and group ids 0. As in FreeBSD's `sys_seteuid` and `sys_setegid`, id 0 is then accepted without a privilege check and changes no credential, so 0 is returned; any other id would change the effective id, which is not modelled, and throws. The ids and privileges a title has on a console are unverified