mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
Three catalog titles stop on Windows when they poll a guest socket for
POLLPRI|POLLOUT to see whether a non-blocking connect has completed:
- PPSA97510 PSXS5 (socket 0x10000002, thread gamedb, 13 s):
what(): poll: POLLPRI is not supported by WSAPoll
- PPSA98510 SwanStation (sockets 0x10000003/0x10000002, threads gamedb
and update): what(): poll: POLLPRI is not supported by WSAPoll
- PPSA99003 ProsperoTV ({0x10000001, POLLPRI|POLLOUT} at 5.3 s):
what(): poll: POLLPRI is not supported by WSAPoll
FreeBSD's sopoll_generic (sys/kern/uipc_socket.c, freebsd-src 8c639945)
reports POLLPRI and POLLRDBAND alike, only while out-of-band data is
pending (so_oobmark or SBS_RCVATMARK). WSAPoll rejects POLLPRI in events
with WSAEINVAL but reports pending urgent data as POLLRDBAND, so on
Windows the guest POLLPRI is now asked of WSAPoll as POLLRDBAND and
POLLIN as POLLRDNORM (Winsock's POLLIN includes POLLRDBAND, which would
report urgent data as readable). Measured on this host: WSAPoll reports
POLLRDBAND after a peer's send(MSG_OOB), waits for it, and clears it once
the byte is read with MSG_OOB. Linux keeps polling POLLPRI on the host.
select needs no change: libkernel's select passes exceptfds as Urgent to
the libSceNet poller, which already asks WSAPoll for POLLRDBAND, and it
does not take libkernel guest sockets on either host.
The guest send and recv refuse MSG_OOB, so urgent data only comes from a
peer outside the guest's sockets; guest_poll now sends it from a host
socket and checks that a connected pair and a non-blocking connect
report POLLOUT and not POLLPRI. TechnicalDebt records where Winsock's
answer differs from FreeBSD's once the stream passes the mark.
Depends on #3649 (poll on file descriptors), whose two commits this
branch carries, since the titles reach this stop only with it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
230 lines
11 KiB
C++
230 lines
11 KiB
C++
#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 {
|
|
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 listen_nid_postfix(int, int);
|
|
int APS5_VABI accept_nid_postfix(int, void*, std::uint32_t*);
|
|
int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t);
|
|
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);
|
|
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;
|
|
constexpr short ReadNormal = 0x40;
|
|
static void TestPipes(int socket) {
|
|
int ends[2];
|
|
Require(pipe_nid_postfix(ends) == 0);
|
|
PollDescriptor empty[2]{{ends[0], In, -1}, {ends[1], Out, -1}};
|
|
Require(poll_nid_postfix(empty, 2, 0) == 1 && empty[0].revents == 0 && empty[1].revents == Out);
|
|
PollDescriptor reader{ends[0], In | ReadNormal, -1};
|
|
const auto start = std::chrono::steady_clock::now();
|
|
Require(poll_nid_postfix(&reader, 1, 50) == 0 && reader.revents == 0);
|
|
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(40));
|
|
Require(write_nid_postfix(ends[1], "wake", 4) == 4);
|
|
Require(poll_nid_postfix(&reader, 1, 1000) == 1 && reader.revents == (In | ReadNormal));
|
|
PollDescriptor writer{ends[1], In, -1};
|
|
Require(poll_nid_postfix(&writer, 1, 0) == 0 && writer.revents == 0);
|
|
PollDescriptor readerOut{ends[0], Out, -1};
|
|
bool threw = false;
|
|
try { poll_nid_postfix(&readerOut, 1, 0); } catch (const std::runtime_error&) { threw = true; }
|
|
Require(threw);
|
|
PollDescriptor mixed[2]{{socket, In, -1}, {ends[0], In, -1}};
|
|
Require(poll_nid_postfix(mixed, 2, 1000) == 1 && mixed[0].revents == 0 && mixed[1].revents == In);
|
|
char buffer[8]{};
|
|
Require(read_nid_postfix(ends[0], buffer, sizeof(buffer)) == 4);
|
|
Require(poll_nid_postfix(mixed, 2, 0) == 0 && mixed[1].revents == 0);
|
|
std::thread late([&] {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
|
Require(write_nid_postfix(ends[1], "x", 1) == 1);
|
|
});
|
|
Require(poll_nid_postfix(mixed, 2, 2000) == 1 && mixed[1].revents == In);
|
|
late.join();
|
|
std::thread lateInfinite([&] {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
|
Require(write_nid_postfix(ends[1], "y", 1) == 1);
|
|
});
|
|
Require(read_nid_postfix(ends[0], buffer, sizeof(buffer)) == 1);
|
|
const auto waitStart = std::chrono::steady_clock::now();
|
|
Require(poll_nid_postfix(&reader, 1, -1) == 1 && reader.revents == (In | ReadNormal));
|
|
Require(std::chrono::steady_clock::now() - waitStart >= std::chrono::milliseconds(20));
|
|
lateInfinite.join();
|
|
Require(read_nid_postfix(ends[0], buffer, sizeof(buffer)) == 1);
|
|
Require(write_nid_postfix(ends[1], "z", 1) == 1);
|
|
Require(close_nid_postfix(ends[1]) == 0);
|
|
Require(poll_nid_postfix(&reader, 1, 0) == 1 && reader.revents == (In | ReadNormal | Hup));
|
|
Require(read_nid_postfix(ends[0], buffer, sizeof(buffer)) == 1);
|
|
Require(poll_nid_postfix(&reader, 1, 0) == 1 && reader.revents == (In | ReadNormal | Hup));
|
|
Require(read_nid_postfix(ends[0], buffer, sizeof(buffer)) == 0);
|
|
PollDescriptor closedPipe{ends[1], In, -1};
|
|
Require(poll_nid_postfix(&closedPipe, 1, 0) == 1 && closedPipe.revents == Invalid);
|
|
Require(close_nid_postfix(ends[0]) == 0);
|
|
int full[2];
|
|
Require(pipe_nid_postfix(full) == 0);
|
|
static char block[4096];
|
|
std::int64_t queued = 0;
|
|
PollDescriptor fill{full[1], Out, -1};
|
|
while (poll_nid_postfix(&fill, 1, 0) == 1) {
|
|
Require(fill.revents == Out && queued < (1 << 20));
|
|
Require(write_nid_postfix(full[1], block, 256) == 256);
|
|
queued += 256;
|
|
}
|
|
Require(queued > 0 && fill.revents == 0);
|
|
while (queued > 0) {
|
|
const std::int64_t taken = read_nid_postfix(full[0], block, static_cast<std::uint64_t>(std::min<std::int64_t>(queued, sizeof(block))));
|
|
Require(taken > 0);
|
|
queued -= taken;
|
|
}
|
|
Require(poll_nid_postfix(&fill, 1, 0) == 1 && fill.revents == Out);
|
|
Require(close_nid_postfix(full[0]) == 0);
|
|
Require(close_nid_postfix(full[1]) == 0);
|
|
int other[2];
|
|
Require(pipe_nid_postfix(other) == 0);
|
|
Require(close_nid_postfix(other[0]) == 0);
|
|
PollDescriptor orphan{other[1], Out, -1};
|
|
Require(poll_nid_postfix(&orphan, 1, 0) == 1 && orphan.revents == Hup);
|
|
orphan.events = In | Out;
|
|
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};
|
|
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);
|
|
PollDescriptor single{listener, In, -1};
|
|
Require(poll_nid_postfix(&single, 1, 0) == 0 && single.revents == 0);
|
|
const auto start = std::chrono::steady_clock::now();
|
|
Require(poll_nid_postfix(&single, 1, 50) == 0);
|
|
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(40));
|
|
const int client = socket_nid_postfix(2, 1, 0);
|
|
Require(client >= 0 && connect_nid_postfix(client, address.data(), address.size()) == 0);
|
|
Require(poll_nid_postfix(&single, 1, 1000) == 1 && single.revents == In);
|
|
const int server = accept_nid_postfix(listener, nullptr, nullptr);
|
|
Require(server >= 0);
|
|
PollDescriptor pair[3]{{server, In, 0}, {-1, In, 7}, {client, Out, 0}};
|
|
Require(poll_nid_postfix(pair, 3, 1000) == 1);
|
|
Require(pair[0].revents == 0 && pair[1].revents == 0 && pair[2].revents == Out);
|
|
const char message[] = "poll";
|
|
Require(send_nid_postfix(client, message, sizeof(message), 0) == sizeof(message));
|
|
pair[2].events = In;
|
|
Require(poll_nid_postfix(pair, 3, 1000) == 1 && pair[0].revents == In && pair[2].revents == 0);
|
|
char buffer[8]{};
|
|
Require(recv_nid_postfix(server, buffer, sizeof(buffer), 0) == sizeof(message));
|
|
Require(close_nid_postfix(client) == 0);
|
|
Require(poll_nid_postfix(pair, 1, 1000) == 1 && (pair[0].revents & (In | Hup)) != 0);
|
|
Require(recv_nid_postfix(server, buffer, sizeof(buffer), 0) == 0);
|
|
PollDescriptor closed[2]{{client, In, 0}, {listener, In, 0}};
|
|
Require(poll_nid_postfix(closed, 2, -1) == 1 && closed[0].revents == Invalid && closed[1].revents == 0);
|
|
Require(poll_nid_postfix(nullptr, 0, 10) == 0);
|
|
Require(poll_nid_postfix(nullptr, 1, 0) == -1 && *__error_nid_postfix() == 14);
|
|
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);
|
|
}
|