Files
AnyPS5/core/libs/tests/GuestPoll.cpp
T
Yunus Shaikh 7c36d37671 fix(libkernel): clear POLLOUT from a hung-up socket in poll on Linux
FreeBSD's pollscan clears POLLOUT whenever POLLHUP is set, for every
descriptor. 64ca6e332 applied that to pipes, but a stream socket whose
peer has closed and whose own sending side is shut down still reported
POLLOUT next to POLLHUP, because Linux's poll sets both. Linux sets
POLLHUP only once both directions are shut, the same condition as
FreeBSD, so poll now clears POLLOUT from any entry that reports POLLHUP
there.
2026-10-25 19:01:02 +01:00

358 lines
19 KiB
C++

#include "prx/libkernel/include/Api.hpp"
#include "prx/libc/include/Api.hpp"
#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 "SceTypes.hpp"
#include <algorithm>
#include <array>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
#include <string>
#include <thread>
#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;
constexpr short WriteBand = 0x100;
constexpr std::int16_t EvfiltUser = -11;
constexpr std::uint16_t EvAdd = 0x0001;
constexpr std::uint16_t EvClear = 0x0020;
constexpr std::uint32_t NoteTrigger = 0x01000000;
static KernelEvent UserEvent(std::uint16_t flags, std::uint32_t fflags) {
KernelEvent change{};
change.ident = 7;
change.filter = EvfiltUser;
change.flags = flags;
change.fflags = fflags;
return change;
}
static void PollsKqueues(int socket) {
const int queue = kqueue_nid_postfix();
Require(queue >= 0);
GuestPollDescriptor kq{queue, static_cast<short>(In | ReadNormal | Out | Priority | ReadBand | WriteBand), -1};
Require(poll_nid_postfix(&kq, 1, 0) == 0 && kq.revents == 0);
const auto added = UserEvent(EvAdd | EvClear, 0);
Require(kevent_nid_postfix(queue, &added, 1, nullptr, 0, nullptr) == 0);
Require(poll_nid_postfix(&kq, 1, 0) == 0 && kq.revents == 0);
const auto start = std::chrono::steady_clock::now();
Require(poll_nid_postfix(&kq, 1, 30) == 0 && kq.revents == 0);
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(25));
std::thread trigger([queue] {
std::this_thread::sleep_for(std::chrono::milliseconds(20));
const auto triggered = UserEvent(0, NoteTrigger);
Require(kevent_nid_postfix(queue, &triggered, 1, nullptr, 0, nullptr) == 0);
});
GuestPollDescriptor mixed[2]{{socket, In, -1}, {queue, static_cast<short>(In | Out), -1}};
Require(poll_nid_postfix(mixed, 2, -1) == 1);
trigger.join();
Require(mixed[0].revents == 0 && mixed[1].revents == In);
Require(poll_nid_postfix(&kq, 1, 0) == 1 && kq.revents == (In | ReadNormal));
KernelEvent events[1]{};
const KernelTimespec zero{0, 0};
Require(kevent_nid_postfix(queue, nullptr, 0, events, 1, &zero) == 1);
Require(poll_nid_postfix(&kq, 1, 0) == 0 && kq.revents == 0);
Require(close_nid_postfix(queue) == 0);
Require(poll_nid_postfix(&kq, 1, 0) == 1 && kq.revents == Invalid);
}
static void PollsFilesAndDirectories(int socket) {
constexpr int ReadWrite = 0x0002, Create = 0x0200, DirectoryFlag = 0x00020000;
const auto stamp = std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
const std::string filePath = "anyps5-poll-file-" + stamp;
const std::string directoryPath = "anyps5-poll-directory-" + stamp;
const int file = open_nid_postfix(filePath.c_str(), ReadWrite | Create, 0644);
Require(file >= 0);
Require(mkdir_nid_postfix(directoryPath.c_str(), 0755) == 0);
const int directory = open_nid_postfix(directoryPath.c_str(), DirectoryFlag, 0);
Require(directory >= 0);
GuestPollDescriptor vnode{file, static_cast<short>(In | Out | ReadNormal | Priority | ReadBand | WriteBand), -1};
Require(poll_nid_postfix(&vnode, 1, 0) == 1 && vnode.revents == (In | Out | ReadNormal));
vnode.events = static_cast<short>(Priority | ReadBand | WriteBand);
Require(poll_nid_postfix(&vnode, 1, 0) == 0 && vnode.revents == 0);
const auto start = std::chrono::steady_clock::now();
Require(poll_nid_postfix(&vnode, 1, 30) == 0);
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(25));
GuestPollDescriptor folder{directory, static_cast<short>(In | Out), -1};
Require(poll_nid_postfix(&folder, 1, -1) == 1 && folder.revents == (In | Out));
GuestPollDescriptor mixed[3]{{socket, In, -1}, {file, Out, -1}, {directory, ReadNormal, -1}};
Require(poll_nid_postfix(mixed, 3, -1) == 2);
Require(mixed[0].revents == 0 && mixed[1].revents == Out && mixed[2].revents == ReadNormal);
Require(close_nid_postfix(directory) == 0 && close_nid_postfix(file) == 0);
Require(rmdir_nid_postfix(directoryPath.c_str()) == 0 && unlink_nid_postfix(filePath.c_str()) == 0);
}
static void TestPipes(int socket) {
int ends[2];
Require(pipe_nid_postfix(ends) == 0);
GuestPollDescriptor 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);
GuestPollDescriptor 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));
const auto shortWaitStart = std::chrono::steady_clock::now();
for (int i = 0; i < 20; ++i) Require(poll_nid_postfix(&reader, 1, 2) == 0 && reader.revents == 0);
const auto shortWait = std::chrono::steady_clock::now() - shortWaitStart;
Require(shortWait >= std::chrono::milliseconds(40) && shortWait < std::chrono::milliseconds(200));
Require(write_nid_postfix(ends[1], "wake", 4) == 4);
Require(poll_nid_postfix(&reader, 1, 1000) == 1 && reader.revents == (In | ReadNormal));
GuestPollDescriptor writer{ends[1], In, -1};
Require(poll_nid_postfix(&writer, 1, 0) == 0 && writer.revents == 0);
GuestPollDescriptor readerOut{ends[0], Out, -1};
bool threw = false;
try { poll_nid_postfix(&readerOut, 1, 0); } catch (const std::runtime_error&) { threw = true; }
Require(threw);
GuestPollDescriptor 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);
GuestPollDescriptor 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;
GuestPollDescriptor 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);
GuestPollDescriptor 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() == 36);
GuestPollDescriptor 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);
GuestPollDescriptor 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);
GuestPollDescriptor 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);
GuestPollDescriptor bands{urgent, In | Priority | Out, -1};
Require(poll_nid_postfix(&bands, 1, 0) == 1 && bands.revents == (Priority | Out));
#ifdef _WIN32
GuestPollDescriptor 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);
}
static void PollsAStreamWhosePeerClosed() {
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 && listen_nid_postfix(listener, 4) == 0);
const int client = socket_nid_postfix(2, 1, 0);
Require(client >= 0 && connect_nid_postfix(client, address.data(), address.size()) == 0);
const int server = accept_nid_postfix(listener, nullptr, nullptr);
Require(server >= 0);
Require(shutdown_nid_postfix(client, 1) == 0);
GuestPollDescriptor wait{server, In, 0};
Require(poll_nid_postfix(&wait, 1, 1000) == 1 && wait.revents == In);
wait = {server, static_cast<short>(In | Out), 0};
Require(poll_nid_postfix(&wait, 1, 1000) == 1 && wait.revents == (In | Out));
wait = {server, ReadNormal, 0};
Require(poll_nid_postfix(&wait, 1, 1000) == 1 && wait.revents == ReadNormal);
wait = {server, Out, 0};
Require(poll_nid_postfix(&wait, 1, 1000) == 1 && wait.revents == Out);
char byte;
Require(recv_nid_postfix(server, &byte, 1, 0) == 0);
Require(shutdown_nid_postfix(server, 1) == 0);
wait = {server, In, 0};
Require(poll_nid_postfix(&wait, 1, 1000) == 1 && wait.revents == (In | Hup));
wait = {client, In, 0};
Require(poll_nid_postfix(&wait, 1, 1000) == 1 && wait.revents == (In | Hup));
for (const int descriptor : {client, server, listener}) Require(close_nid_postfix(descriptor) == 0);
}
#ifndef _WIN32
static void PollsAHungUpSocketWithoutPollout() {
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 && listen_nid_postfix(listener, 1) == 0);
const int client = socket_nid_postfix(2, 1, 0);
Require(client >= 0 && connect_nid_postfix(client, address.data(), address.size()) == 0);
const int server = accept_nid_postfix(listener, nullptr, nullptr);
Require(server >= 0);
Require(close_nid_postfix(client) == 0);
char byte = 0;
GuestPollDescriptor ended{server, In, 0};
Require(poll_nid_postfix(&ended, 1, 1000) == 1);
Require(recv_nid_postfix(server, &byte, 1, 0) == 0);
Require(shutdown_nid_postfix(server, 1) == 0);
GuestPollDescriptor hungUp{server, static_cast<short>(In | Out), 0};
Require(poll_nid_postfix(&hungUp, 1, 1000) == 1 && hungUp.revents == (In | Hup));
Require(close_nid_postfix(server) == 0 && close_nid_postfix(listener) == 0);
}
#endif
int main() {
PollsUrgentData();
PollsAStreamWhosePeerClosed();
#ifndef _WIN32
PollsAHungUpSocketWithoutPollout();
#endif
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);
GuestPollDescriptor 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);
GuestPollDescriptor 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);
GuestPollDescriptor 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);
const auto shortWaitStart = std::chrono::steady_clock::now();
for (int i = 0; i < 20; ++i) Require(poll_nid_postfix(nullptr, 0, 1) == 0);
const auto shortWait = std::chrono::steady_clock::now() - shortWaitStart;
Require(shortWait >= std::chrono::milliseconds(20) && shortWait < std::chrono::milliseconds(200));
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);
PollsFilesAndDirectories(listener);
Require(close_nid_postfix(server) == 0);
for (const char* path : {"/dev/urandom", "/dev/random"}) {
const int random = open_nid_postfix(path, 0, 0);
Require(random >= 0);
GuestPollDescriptor device{random, static_cast<short>(In | Out | Priority), -1};
Require(poll_nid_postfix(&device, 1, 0) == 1 && device.revents == In);
device.events = static_cast<short>(ReadNormal | Out);
Require(poll_nid_postfix(&device, 1, 0) == 1 && device.revents == ReadNormal);
device.events = static_cast<short>(Out | Priority | ReadBand | WriteBand);
Require(poll_nid_postfix(&device, 1, -1) == 1 && device.revents == (Out | Priority | ReadBand | WriteBand));
GuestPollDescriptor mixed[2]{{listener, In, 0}, {random, Out, 0}};
Require(poll_nid_postfix(mixed, 2, -1) == 1 && mixed[0].revents == 0 && mixed[1].revents == Out);
Require(close_nid_postfix(random) == 0);
}
GuestPollDescriptor console[3]{{0, static_cast<short>(In | ReadNormal | Out), -1}, {1, static_cast<short>(In | Out | WriteBand), -1}, {2, Out, -1}};
Require(poll_nid_postfix(console, 3, 0) == 2);
Require(console[0].revents == 0 && console[1].revents == Out && console[2].revents == Out);
GuestPollDescriptor input{0, In, -1};
const auto inputStart = std::chrono::steady_clock::now();
Require(poll_nid_postfix(&input, 1, 30) == 0 && input.revents == 0);
Require(std::chrono::steady_clock::now() - inputStart >= std::chrono::milliseconds(25));
PollsKqueues(listener);
Require(close_nid_postfix(listener) == 0);
}