diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index d1c05db7e6..106a7501bc 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -716,6 +716,17 @@ int SetNonBlocking(Socket& socket) { socket.options.nonblocking = true; return 0; } +bool ReadableNow(const Socket& socket) { + NativePollDescriptor entry{}; + entry.fd = socket.value; +#ifdef _WIN32 + entry.events = POLLRDNORM; + return WSAPoll(&entry, 1, 0) > 0; +#else + entry.events = POLLIN; + return ::poll(&entry, 1, 0) > 0; +#endif +} int ReceiveAnsweredHere(Socket& socket, std::uint64_t length) { #ifdef _WIN32 { @@ -723,7 +734,8 @@ int ReceiveAnsweredHere(Socket& socket, std::uint64_t length) { if (socket.receiveShut) return 0; } #endif - if (length != 0 || socket.type != 1) return -1; + if (length != 0) return -1; + if (socket.type != 1) return ReadableNow(socket) ? -1 : 0; sockaddr_storage peer{}; socklen_t size = sizeof(peer); return ::getpeername(socket.value, reinterpret_cast(&peer), &size) == 0 ? 0 : NativeError(); @@ -1593,22 +1605,26 @@ std::int64_t APS5_VABI recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, return Fail(55); } sockaddr_storage native{}; - socklen_t size = sizeof(native); - if (const int waited = WaitForTransfer(*socket, false, flags)) return Fail(waited); - std::int64_t received = ::recvfrom(socket->value, buffer.data(), static_cast(buffer.size()), - NativeReceiveFlags(flags, socket->type), reinterpret_cast(&native), &size); - if (received < 0) { + socklen_t size = 0; + std::int64_t received = 0; + if (total != 0 || socket->type == 1 || ReadableNow(*socket)) { + size = sizeof(native); + if (const int waited = WaitForTransfer(*socket, false, flags)) return Fail(waited); + received = ::recvfrom(socket->value, buffer.data(), static_cast(buffer.size()), + NativeReceiveFlags(flags, socket->type), reinterpret_cast(&native), &size); + if (received < 0) { #ifdef _WIN32 - if (!records || WSAGetLastError() != WSAEMSGSIZE) return Fail(NativeError()); - received = static_cast(buffer.size()); + if (!records || WSAGetLastError() != WSAEMSGSIZE) return Fail(NativeError()); + received = static_cast(buffer.size()); #else - return Fail(NativeError()); + return Fail(NativeError()); #endif + } } - header.flags = 0; + header.flags = flags; if (received > total) { received = total; - header.flags = 0x10; + header.flags |= 0x10; } std::size_t offset = 0; for (const auto& vector : vectors) { diff --git a/core/libs/tests/GuestDatagramReceive.cpp b/core/libs/tests/GuestDatagramReceive.cpp index 2c7bc9e0c5..d719b7719e 100644 --- a/core/libs/tests/GuestDatagramReceive.cpp +++ b/core/libs/tests/GuestDatagramReceive.cpp @@ -140,6 +140,21 @@ static void Check(bool net, bool from, int family) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } while (std::chrono::steady_clock::now() < deadline); Require(result == 0 && byte == '!', "empty datagram"); + if (!net) { + send("hello", 5); + const auto ready = std::chrono::steady_clock::now() + std::chrono::seconds(1); + do { + result = receive(&byte, 1, 2, nullptr, nullptr); + if (result >= 0 || *error != 35) break; + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } while (std::chrono::steady_clock::now() < ready); + Require(result == 1, "wait for the datagram"); + Require(receive(&byte, 0, 2, nullptr, nullptr) == 0 && receive(&byte, 1, 2, nullptr, nullptr) == 1, "a zero-length peek keeps the datagram"); + Require(receive(&byte, 0, 0, nullptr, nullptr) == 0, "a zero-length receive returns 0"); + Require(receive(&byte, 1, 0, nullptr, nullptr) < 0 && *error == 35, "a zero-length receive discards the datagram"); + Require(fcntl_nid_postfix(receiver, 4, 2) == 0, "make receiver blocking"); + Require(receive(&byte, 0, 0, nullptr, nullptr) == 0, "a zero-length receive on an empty queue returns 0 without waiting"); + } Require(closeSocket(receiver) == 0, "close receiver"); Require(closeSocket(sender) == 0, "close sender"); } diff --git a/core/libs/tests/GuestUdp.cpp b/core/libs/tests/GuestUdp.cpp index 7efa4731f4..a4e6abd15c 100644 --- a/core/libs/tests/GuestUdp.cpp +++ b/core/libs/tests/GuestUdp.cpp @@ -254,6 +254,9 @@ int main() { source.fill(0); unsigned char control[8]{}; Msghdr incoming{source.data(), static_cast(source.size()), pieces, 2, control, sizeof(control), -1}; + Require(recvmsg_nid_postfix(receiver, &incoming, 2) == 14 && incoming.flags == 2); + incoming.nameLength = static_cast(source.size()); + incoming.controlLength = sizeof(control); Require(recvmsg_nid_postfix(receiver, &incoming, 0) == 14); Require(std::memcmp(first, "scat", 4) == 0 && std::memcmp(second, "ter gather", 10) == 0); Require(incoming.flags == 0 && incoming.controlLength == 0 && incoming.nameLength == 16 && source[1] == 2 && source[4] == 127); @@ -262,9 +265,15 @@ int main() { char shortBuffer[5]{}; Iovec single{shortBuffer, sizeof(shortBuffer)}; Msghdr truncated{nullptr, 0, &single, 1, nullptr, 0, 0}; - Require(recvmsg_nid_postfix(receiver, &truncated, 0) == 5); - Require(std::memcmp(shortBuffer, "scatt", 5) == 0 && truncated.flags == 0x10); + Require(recvmsg_nid_postfix(receiver, &truncated, 2) == 5 && truncated.flags == 0x12); + Require(recvmsg_nid_postfix(receiver, &truncated, 0x80) == 5); + Require(std::memcmp(shortBuffer, "scatt", 5) == 0 && truncated.flags == 0x90); Require(recvmsg_nid_postfix(receiver, &truncated, 0) == -1 && *__error_nid_postfix() == 35); + Iovec none{shortBuffer, 0}; + Msghdr empty{nullptr, 0, &none, 1, nullptr, 0, -1}; + Require(fcntl_nid_postfix(receiver, 4, 2) == 0); + Require(recvmsg_nid_postfix(receiver, &empty, 0) == 0 && empty.flags == 0); + Require(fcntl_nid_postfix(receiver, 4, 6) == 0); Msghdr oversized{nullptr, 0, parts, 1025, nullptr, 0, 0}; Require(sendmsg_nid_postfix(sender, &oversized, 0) == -1 && *__error_nid_postfix() == 40); Require(recvmsg_nid_postfix(receiver, nullptr, 0) == -1 && *__error_nid_postfix() == 14); diff --git a/docs/dev/debt/libSceNet.md b/docs/dev/debt/libSceNet.md index b9ad83d736..1e685a25a3 100644 --- a/docs/dev/debt/libSceNet.md +++ b/docs/dev/debt/libSceNet.md @@ -26,6 +26,7 @@ ### Functional +- [sceNetRecv/sceNetRecvfrom](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) 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; the console measurement covered only libkernel_sys `recv` and `recvfrom`, not libSceNet. - [sceNetUsleep](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) prints nothing for a negative count, where the console prints `[LIBNET] usleep ` to standard output before failing with `EINVAL`. What an interrupted sleep returns is unmeasured: AnyPS5 returns the result of libkernel's `sceKernelUsleep`, a cancellation point. The signed 32-bit count (the upper half of the register is ignored) and the 0 result were measured on a PS5 (firmware 13.60) - [sceNetIoctl](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - on a `sceNetSocket` socket only `FIONBIO` and `FIONREAD` are handled and other requests fail with `ENOTTY`, as in libkernel's `ioctl`, though FreeBSD 11.4's [`soo_ioctl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/sys_socket.c#L156-L262) handles more; not measured on a console. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/60c42b925c3d1e00008621b8587819999260ec31/src/core/libraries/net/net.cpp#L1839) (PS4) returns `EINVAL` for other requests. `FIONBIO` sets `SO_NBIO`, assumed to be `soo_ioctl`'s `SS_NBIO`. `FIONREAD` asks the host, which on Linux counts only the next datagram where `soo_ioctl` counts all. Libkernel's own sockets go to its `ioctl`; other descriptors get `EBADF`. - [sceNetResolverStartAton6](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) reverse-resolves a 16-byte IPv6 address through the host resolver (`getnameinfo` with `NI_NAMEREQD`) into the caller's buffer of `len` bytes, as `sceNetResolverStartAton` does for IPv4. The `(rid, addr, hostname, len, timeout, retry, flags)` signature comes from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Socket.cs#L992-L997). An address with no name, or a name longer than `len - 1` bytes, fails with `RESOLVER_ENODNS` (`0x804101E1`) and leaves the buffer untouched; whether the console truncates instead is not verified. Abort flag 2 (ATON preservation) fails the next call with `EINTR` as it does `sceNetResolverStartAton`. Timeout and retry are ignored, and any nonzero flags throw because their meaning is unknown diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index a8782778c9..3501e709d4 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -202,11 +202,11 @@ - [nmount](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) throws: mounting file systems is not supported. Prospero Themes (PPSA90210) imports it and fails to load without it; it opens its theme library without calling it, and its strings (`/dev/ssd0.system_ex`, `/system_ex`, `exfatfs`, `fstype`, `fspath`, `from`, `ignoreacl`) point to remounting the console's `/system_ex` partition to write theme files there. - [socketpair](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) - the pair is the host's `AF_UNIX` pair on Linux and macOS and, on Windows, a loopback TCP connection for `SOCK_STREAM` or two connected UDP sockets for `SOCK_DGRAM`; `SOCK_SEQPACKET` throws on Windows and macOS, which has no such `AF_UNIX` pair. 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 the first stream `send` after the peer closed succeeds on the loopback connection, where the console returns `EPIPE`; the sends after it report `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 `POLLIN` after the peer's `shutdown(SHUT_WR)` or `close`, as the console does after a `shutdown(SHUT_WR)`; after the peer's `close` the console reports `POLLIN` with `POLLHUP`, because `unp_drop` disconnects the other end, and the loopback connection cannot tell that from a shutdown, so no `POLLHUP` is reported - Guest sockets on macOS ([the equeue wait, kevent, fstat and ioctl](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp), [gethostbyname](../../../core/libs/prx/libScePosixForWebKit/Resolver.cpp)): macOS has no `ppoll`, so the wait uses `poll` with the timeout rounded up to a millisecond; it has no `POLLRDHUP`, so a stream that the peer closed is found by peeking one byte when it is readable; `FIONREAD` on a datagram socket, and the byte count that `fstat` and `kevent` report for it, is `SO_NREAD`, the size of the next datagram, because macOS adds the source address to `FIONREAD`; and `gethostbyname` replaces `gethostbyname_r`, which macOS lacks. -- [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. - [recvmsg](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) loses received stream data on an output copy fault: outputs are checked after the host receive. FreeBSD 11.4 leaves unread stream data queued, though it also drops a datagram on a copy fault ([`soreceive_dgram`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L2308-L2312), [`soreceive_generic`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L1758-L1774)). A Linux `process_vm_readv` or `process_vm_writev` failure other than `EFAULT` throws - [MSG_DONTWAIT](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) in `send`, `sendto`, `sendmsg`, `recv`, `recvfrom` and `recvmsg` makes that one call non-blocking through the host's `MSG_DONTWAIT`, as FreeBSD 11.4's `soreceive_generic` returns `EWOULDBLOCK` for it (`sys/kern/uipc_socket.c`). Winsock has no per-call non-blocking flag, so on Windows the call first asks `WSAPoll` with a zero timeout whether the socket is ready and fails with `EAGAIN` when it is not. The check and the transfer are two host calls: a second thread that takes the data in between can leave a `recv` blocked, and a `send` of more bytes than the socket buffer holds can still block until it has sent them, where FreeBSD's `sosend_generic` returns a partial count - [MSG_WAITALL](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) in `recv`, `recvfrom` and `recvmsg` on a stream socket is passed to the host, which returns when the buffer is full, the peer closes or an error occurs, as FreeBSD 11.4's [`soreceive_generic`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L1855-L1860) does; a datagram socket ignores it. It is not measured on a console, a nonblocking socket and a call that also passes `MSG_PEEK` follow the host's rules, and `SO_RCVTIMEO` bounds only the wait for the first data. Any other flag still fails with `EOPNOTSUPP`. -- [recv and recvfrom](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) answer without the host a zero-length receive on a stream socket (0, or `ENOTCONN` when it is not connected, [L1566-L1578](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L1566-L1578)) and, on Windows only, a receive on a socket whose read side `shutdown` closed (0, as [`soreceive_generic`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L1556-L1562) returns at `SBS_CANTRCVMORE`); Winsock fails the second with `EPIPE` and blocks on the first. Linux and macOS still return data buffered before the `shutdown`, where FreeBSD discards it. `recvmsg` still asks the host in both cases. Not measured on a console. +- [recv and recvfrom](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) answer some cases without the host. A zero-length receive on a stream socket returns 0, or `ENOTCONN` when it is not connected ([L1566-L1578](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L1566-L1578)), as on a PS5 (firmware 13.60). On an empty datagram socket it returns 0 at once; the console was measured only with `MSG_DONTWAIT`, and the blocking case follows FreeBSD 11.4's `soreceive_dgram`, which returns 0 before waiting. `recvmsg` answers the empty datagram socket the same way but asks the host in the stream cases. A datagram another thread takes after the check makes a zero-length receive wait +- [recv and recvfrom after `shutdown(SHUT_RD)`](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) return 0 on Windows ([`soreceive_generic`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_socket.c#L1556-L1562)) where Winsock fails with `EPIPE`; Linux and macOS still return data buffered before it, which the console discards. - [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). - [bind, connect, sendto](../../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) check a socket address length as FreeBSD 11.4's [`getsockaddr`](https://github.com/freebsd/freebsd-src/blob/release/11.4.0/sys/kern/uipc_syscalls.c) and the `sa_len` test of `tcp_usr_connect` and `in_pcbconnect` do: below 2 or an `AF_INET` address below 16 or an `AF_INET6` address below 28 bytes fails with `EINVAL`, above 255 with `ENAMETOOLONG`. `sendto` does not check an address longer than `sockaddr_in` on an `AF_INET` datagram socket, where `udp_output` answers `EINVAL` unless the socket has a local address and port and the destination is neither `INADDR_ANY` nor `INADDR_BROADCAST`, nor on an unconnected stream socket, which FreeBSD connects implicitly through `tcp_connect` and its length test. An address of another family still fails with `EAFNOSUPPORT`, as the console's behaviour for it is not known. None of this was measured on a console. - [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 unless the socket has `SO_NOSIGPIPE` set, and the guest cannot ignore it: `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; nothing is raised 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