Merge pull request #4525 from Lorkydey/fix/poll-priority-windows
fix(libkernel): support POLLPRI on Windows sockets
This commit is contained in:
commit
18414447b9
|
|
@ -1001,6 +1001,7 @@ if(BUILD_TESTING)
|
|||
add_test(NAME guest_poll COMMAND guest_poll_tests)
|
||||
set_tests_properties(guest_poll PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
target_link_libraries(guest_poll_tests PRIVATE ws2_32)
|
||||
set_tests_properties(guest_poll PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -393,8 +393,15 @@ struct PollFlag {
|
|||
constexpr short GuestPollPriority = 0x2;
|
||||
constexpr short GuestPollInvalid = 0x20;
|
||||
constexpr short GuestPollAccepted = 0x1 | 0x2 | 0x4 | 0x8 | 0x10 | 0x20 | 0x40 | 0x80 | 0x100;
|
||||
#ifdef _WIN32
|
||||
constexpr short NativePollIn = POLLRDNORM;
|
||||
constexpr short NativePollPriority = POLLRDBAND;
|
||||
#else
|
||||
constexpr short NativePollIn = POLLIN;
|
||||
constexpr short NativePollPriority = POLLPRI;
|
||||
#endif
|
||||
constexpr PollFlag RequestFlags[] = {
|
||||
{0x1, POLLIN}, {0x2, POLLPRI}, {0x4, POLLOUT}, {0x40, POLLRDNORM}, {0x80, POLLRDBAND}, {0x100, POLLWRBAND}};
|
||||
{0x1, NativePollIn}, {GuestPollPriority, NativePollPriority}, {0x4, POLLOUT}, {0x40, POLLRDNORM}, {0x80, POLLRDBAND}, {0x100, POLLWRBAND}};
|
||||
constexpr PollFlag StatusFlags[] = {{0x8, POLLERR}, {0x10, POLLHUP}, {GuestPollInvalid, POLLNVAL}};
|
||||
std::shared_ptr<Socket> Find(int descriptor) {
|
||||
std::lock_guard lock(socketsMutex);
|
||||
|
|
@ -1199,9 +1206,6 @@ int APS5_VABI poll_nid_postfix(GuestPollDescriptor* descriptors, std::uint32_t c
|
|||
pipes.push_back(i);
|
||||
continue;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
if (entry.events & GuestPollPriority) throw std::runtime_error("poll: POLLPRI is not supported by WSAPoll");
|
||||
#endif
|
||||
NativePollDescriptor request{};
|
||||
request.fd = socket->value;
|
||||
for (const auto& flag : RequestFlags)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,18 @@
|
|||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
struct PollDescriptor {
|
||||
|
|
@ -26,10 +35,22 @@ 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);
|
||||
std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t);
|
||||
int APS5_VABI fcntl_nid_postfix(int, int, ...);
|
||||
int* APS5_VABI __error_nid_postfix();
|
||||
}
|
||||
#ifdef _WIN32
|
||||
using NativeSocket = SOCKET;
|
||||
constexpr NativeSocket NativeInvalid = INVALID_SOCKET;
|
||||
static void CloseNative(NativeSocket socket) { closesocket(socket); }
|
||||
#else
|
||||
using NativeSocket = int;
|
||||
constexpr NativeSocket NativeInvalid = -1;
|
||||
static void CloseNative(NativeSocket socket) { close(socket); }
|
||||
#endif
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
constexpr short In = 0x1;
|
||||
constexpr short Priority = 0x2;
|
||||
constexpr short ReadBand = 0x80;
|
||||
constexpr short Out = 0x4;
|
||||
constexpr short Hup = 0x10;
|
||||
constexpr short Invalid = 0x20;
|
||||
|
|
@ -109,7 +130,54 @@ static void TestPipes(int socket) {
|
|||
Require(poll_nid_postfix(&orphan, 1, 0) == 1 && orphan.revents == (In | Hup));
|
||||
Require(close_nid_postfix(other[1]) == 0);
|
||||
}
|
||||
static void PollsUrgentData() {
|
||||
const int listener = socket_nid_postfix(2, 1, 0);
|
||||
Require(listener >= 0);
|
||||
std::array<unsigned char, 16> 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();
|
||||
Require(getsockname_nid_postfix(listener, address.data(), &size) == 0);
|
||||
Require(listen_nid_postfix(listener, 4) == 0);
|
||||
const int client = socket_nid_postfix(2, 1, 0);
|
||||
Require(client >= 0 && fcntl_nid_postfix(client, 4, 4) == 0);
|
||||
if (connect_nid_postfix(client, address.data(), address.size()) != 0)
|
||||
Require(*__error_nid_postfix() == 35 || *__error_nid_postfix() == 36);
|
||||
PollDescriptor connecting{client, Priority | Out, -1};
|
||||
Require(poll_nid_postfix(&connecting, 1, 1000) == 1 && connecting.revents == Out);
|
||||
const int server = accept_nid_postfix(listener, nullptr, nullptr);
|
||||
Require(server >= 0);
|
||||
PollDescriptor pair[2]{{server, Priority | Out, -1}, {client, In | Priority | Out | ReadBand, -1}};
|
||||
Require(poll_nid_postfix(pair, 2, 0) == 2 && pair[0].revents == Out && pair[1].revents == Out);
|
||||
char byte = 'u';
|
||||
Require(send_nid_postfix(client, &byte, 1, 1) == -1 && *__error_nid_postfix() == 45);
|
||||
Require(recv_nid_postfix(server, &byte, 1, 1) == -1 && *__error_nid_postfix() == 45);
|
||||
const NativeSocket peer = ::socket(AF_INET, SOCK_STREAM, 0);
|
||||
Require(peer != NativeInvalid);
|
||||
sockaddr_in native{};
|
||||
native.sin_family = AF_INET;
|
||||
std::memcpy(&native.sin_port, address.data() + 2, 2);
|
||||
std::memcpy(&native.sin_addr, address.data() + 4, 4);
|
||||
Require(::connect(peer, reinterpret_cast<const sockaddr*>(&native), sizeof(native)) == 0);
|
||||
const int urgent = accept_nid_postfix(listener, nullptr, nullptr);
|
||||
Require(urgent >= 0);
|
||||
PollDescriptor waiting{urgent, Priority, -1};
|
||||
Require(poll_nid_postfix(&waiting, 1, 0) == 0 && waiting.revents == 0);
|
||||
Require(::send(peer, "u", 1, MSG_OOB) == 1);
|
||||
Require(poll_nid_postfix(&waiting, 1, 1000) == 1 && waiting.revents == Priority);
|
||||
PollDescriptor bands{urgent, In | Priority | Out, -1};
|
||||
Require(poll_nid_postfix(&bands, 1, 0) == 1 && bands.revents == (Priority | Out));
|
||||
#ifdef _WIN32
|
||||
PollDescriptor band{urgent, ReadBand, -1};
|
||||
Require(poll_nid_postfix(&band, 1, 0) == 1 && band.revents == ReadBand);
|
||||
#endif
|
||||
CloseNative(peer);
|
||||
Require(close_nid_postfix(urgent) == 0);
|
||||
Require(close_nid_postfix(server) == 0);
|
||||
Require(close_nid_postfix(client) == 0);
|
||||
Require(close_nid_postfix(listener) == 0);
|
||||
}
|
||||
int main() {
|
||||
PollsUrgentData();
|
||||
const int listener = socket_nid_postfix(2, 1, 0);
|
||||
Require(listener >= 0 && listener < 1024);
|
||||
std::array<unsigned char, 16> address{16, 2, 0, 0, 127, 0, 0, 1};
|
||||
|
|
|
|||
|
|
@ -142,7 +142,8 @@
|
|||
- [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. `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
|
||||
- [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. `WSAPoll` rejects `POLLPRI`, so on Windows a socket's `POLLPRI` is asked as `POLLRDBAND` and `POLLIN` as `POLLRDNORM`; Winsock reports urgent data until it is read with `MSG_OOB`, where FreeBSD 11.4's `sopoll_generic` stops once the normal stream passes the mark. Guest `send` and `recv` refuse `MSG_OOB` with `EOPNOTSUPP`, so urgent data can only come from an outside peer and Windows keeps reporting `POLLPRI` after the stream passes the mark. 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 reports `POLLNVAL`, and 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`), 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
|
||||
- [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:<port>` stays `127.0.0.1:<port>`), 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
|
||||
- [madvise](../../../core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp) (libkernel) accepts `MADV_PROTECT` (10) as a no-op hint without modifying process protection. FreeBSD 11's `kern_madvise` (`sys/vm/vm_mmap.c`) handles it before the address check, ignores the address and length, and calls `kern_procctl(..., PROC_SPROTECT, PPROT_SET)`, which requires `PRIV_VM_MADV_PROTECT` and fails with `EPERM` without it. Returning 0 unconditionally assumes the calling process is privileged. Console behaviour is unverified
|
||||
|
|
|
|||
Loading…
Reference in New Issue