Files
AnyPS5/core/libs/tests/GuestDup.cpp
T
screammandClaude Fable 5.1 591e8ddadc feat(libkernel): support fcntl on non-socket descriptors
fcntl and sceKernelFcntl answered EBADF (sceKernelFcntl threw) for
every descriptor outside the libSceNet socket table, so files, pipes,
directories and descriptors 0 to 2 could not be queried or changed.

Observed: Yamagi Quake II (PPSA99007) stops with
"Sys_Error: change stdin to blocking Bad file descriptor", from
fcntl(fileno(stdin), F_SETFL, fcntl(0, F_GETFL, 0) & ~FNDELAY)
(yquake2 src/backends/unix/system.c:81-86).

Predicted from the titles' sources and decoded call sites: XPSemu
(PPSA97358, 97 fcntl sites: F_GETFD 12, F_SETFD 20, F_GETFL 8,
F_SETFL 9, F_DUPFD 1, F_DUPFD_CLOEXEC 7, F_GETLK 9) fails in
qemu_init_main_loop, where its pipe2 (pipe plus F_SETFD FD_CLOEXEC) and
glib's g_unix_set_fd_nonblocking (F_GETFL, F_SETFL) build the event
notifier (util/event_notifier-posix.c:44-67); VLC (PPSA85300, 43 sites,
F_RDAHEAD on every file it opens), RetroArch (PPSA99169, 17),
Porpoise (PPSA99764, 13) and Ruffle (9) call it on files and pipes.

The commands follow FreeBSD 11.4 kern_fcntl and kern_dup
(sys/kern/kern_descrip.c:479-799, 812, release/11.4.0 8c639945):
F_GETFD/F_SETFD keep the close-on-exec flag per descriptor, F_GETFL
reports the host access mode with the status flags, F_SETFL changes
only O_NONBLOCK, O_APPEND, O_ASYNC, O_FSYNC, O_DIRECT and FRDAHEAD,
F_DUPFD/F_DUPFD_CLOEXEC give the lowest free descriptor at or above the
start through dup (EINVAL for a negative start), F_DUP2FD goes through
dup2, F_RDAHEAD/F_READAHEAD set FRDAHEAD on files (EBADF on pipes,
ENOTTY elsewhere), and unknown commands fail with EINVAL. Status flags
live in a guest table shared by dup, dup2 and F_DUPFD like an open file
description. O_NONBLOCK switches a pipe (PIPE_NOWAIT on Windows, where an
empty read and a full write now report EAGAIN; the host flag on Linux).
Record locks use the host POSIX locks on Linux and throw on Windows.
Changes the host cannot model throw naming the flag and descriptor kind.
Sockets keep their path unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-12 12:17:25 +05:30

218 lines
9.1 KiB
C++

#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
extern "C" {
int APS5_VABI dup_nid_postfix(int);
int APS5_VABI dup2_nid_postfix(int, int);
int APS5_VABI pipe_nid_postfix(int*);
int APS5_VABI close_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 socketpair_nid_postfix(int, int, int, int*);
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 open_nid_postfix(const char*, int, int);
int APS5_VABI fcntl_nid_postfix(int, int, ...);
int* APS5_VABI __error_nid_postfix();
}
static void Require(bool value) { if (!value) std::abort(); }
constexpr int GuestEbadf = 9;
constexpr int Unix = 1;
constexpr int Stream = 1;
constexpr int FirstSocket = 0x10000000;
static void RequireFailure(int result, int error) {
Require(result == -1);
Require(*__error_nid_postfix() == error);
}
static void RequirePipeCarries(int writer, int reader, const char* text) {
const auto length = static_cast<std::int64_t>(std::strlen(text));
Require(write_nid_postfix(writer, text, length) == length);
char buffer[16] = {};
Require(read_nid_postfix(reader, buffer, sizeof(buffer)) == length);
Require(std::memcmp(buffer, text, static_cast<std::size_t>(length)) == 0);
}
static void RequireSocketCarries(int writer, int reader, const char* text) {
const auto length = std::strlen(text);
Require(send_nid_postfix(writer, text, length, 0) == static_cast<std::int64_t>(length));
char buffer[16] = {};
Require(recv_nid_postfix(reader, buffer, sizeof(buffer), 0) == static_cast<std::int64_t>(length));
Require(std::memcmp(buffer, text, length) == 0);
}
template <typename TCall> static bool Throws(TCall call) {
try {
call();
} catch (const std::runtime_error&) {
return true;
}
return false;
}
static void DupOutlivesOriginal() {
int pipe[2];
Require(pipe_nid_postfix(pipe) == 0);
const int duplicate = dup_nid_postfix(pipe[1]);
Require(duplicate >= 0 && duplicate < FirstSocket && duplicate != pipe[1]);
Require(close_nid_postfix(pipe[1]) == 0);
RequirePipeCarries(duplicate, pipe[0], "dup");
Require(close_nid_postfix(duplicate) == 0);
char end = 0;
Require(read_nid_postfix(pipe[0], &end, 1) == 0);
Require(close_nid_postfix(pipe[0]) == 0);
}
static void Dup2ReplacesTarget() {
int first[2];
int second[2];
Require(pipe_nid_postfix(first) == 0);
Require(pipe_nid_postfix(second) == 0);
Require(dup2_nid_postfix(first[1], second[1]) == second[1]);
RequirePipeCarries(second[1], first[0], "dup2");
char end = 0;
Require(read_nid_postfix(second[0], &end, 1) == 0);
Require(dup2_nid_postfix(first[1], first[1]) == first[1]);
for (const int descriptor : {first[0], first[1], second[0], second[1]}) Require(close_nid_postfix(descriptor) == 0);
}
#ifdef _WIN32
static bool g_invalidParameter = false;
static void RecordInvalidParameter(const wchar_t*, const wchar_t*, const wchar_t*, unsigned, std::uintptr_t) { g_invalidParameter = true; }
#endif
static void BadDescriptors() {
int pipe[2];
Require(pipe_nid_postfix(pipe) == 0);
const int closed = pipe[1];
Require(close_nid_postfix(closed) == 0);
RequireFailure(dup_nid_postfix(-1), GuestEbadf);
RequireFailure(dup_nid_postfix(closed), GuestEbadf);
RequireFailure(dup2_nid_postfix(closed, pipe[0]), GuestEbadf);
RequireFailure(dup2_nid_postfix(closed, closed), GuestEbadf);
RequireFailure(dup2_nid_postfix(pipe[0], -1), GuestEbadf);
RequireFailure(dup2_nid_postfix(-1, pipe[0]), GuestEbadf);
RequireFailure(dup_nid_postfix(FirstSocket + 0x0ffffff0), GuestEbadf);
RequireFailure(dup2_nid_postfix(FirstSocket + 0x0ffffff0, FirstSocket + 0x0ffffff1), GuestEbadf);
#ifdef _WIN32
const auto previous = _set_invalid_parameter_handler(RecordInvalidParameter);
#endif
RequireFailure(close_nid_postfix(closed), GuestEbadf);
RequireFailure(close_nid_postfix(-1), GuestEbadf);
#ifdef _WIN32
_set_invalid_parameter_handler(previous);
Require(!g_invalidParameter);
#endif
Require(close_nid_postfix(pipe[0]) == 0);
}
static void SocketDuplicates() {
int pair[2];
Require(socketpair_nid_postfix(Unix, Stream, 0, pair) == 0);
const int duplicate = dup_nid_postfix(pair[0]);
Require(duplicate >= FirstSocket && duplicate != pair[0] && duplicate != pair[1]);
Require(close_nid_postfix(pair[0]) == 0);
RequireSocketCarries(duplicate, pair[1], "socket");
const int target = duplicate + 0x1000;
Require(dup2_nid_postfix(duplicate, target) == target);
Require(close_nid_postfix(duplicate) == 0);
RequireSocketCarries(target, pair[1], "moved");
Require(dup2_nid_postfix(target, target) == target);
int next[2];
Require(socketpair_nid_postfix(Unix, Stream, 0, next) == 0);
Require(next[0] > target && next[1] > target);
RequireSocketCarries(target, pair[1], "kept");
Require(dup2_nid_postfix(next[0], target) == target);
RequireSocketCarries(target, next[1], "over");
Require(Throws([&] { dup2_nid_postfix(target, 1); }));
int pipe[2];
Require(pipe_nid_postfix(pipe) == 0);
Require(Throws([&] { dup2_nid_postfix(pipe[0], target); }));
for (const int descriptor : {target, pair[1], next[0], next[1], pipe[0], pipe[1]}) Require(close_nid_postfix(descriptor) == 0);
}
static void RandomDeviceDuplicates() {
const int random = open_nid_postfix("/dev/urandom", 0, 0);
Require(random >= 0);
const int dupRandom = dup_nid_postfix(random);
Require(dupRandom >= 0 && dupRandom != random);
unsigned char randomBuf1[16] = {};
unsigned char randomBuf2[16] = {};
Require(read_nid_postfix(random, randomBuf1, sizeof(randomBuf1)) == sizeof(randomBuf1));
Require(read_nid_postfix(dupRandom, randomBuf2, sizeof(randomBuf2)) == sizeof(randomBuf2));
Require(std::memcmp(randomBuf1, randomBuf2, sizeof(randomBuf1)) != 0);
Require(dup2_nid_postfix(random, random) == random);
Require(read_nid_postfix(random, randomBuf1, sizeof(randomBuf1)) == sizeof(randomBuf1));
int pipe[2];
Require(pipe_nid_postfix(pipe) == 0);
Require(write_nid_postfix(pipe[1], "hello", 5) == 5);
Require(dup2_nid_postfix(pipe[0], random) == random);
char textBuf[8] = {};
Require(read_nid_postfix(random, textBuf, 5) == 5);
Require(std::memcmp(textBuf, "hello", 5) == 0);
const int targetHost = pipe[0];
Require(dup2_nid_postfix(dupRandom, targetHost) == targetHost);
unsigned char randomBuf3[16] = {};
Require(read_nid_postfix(targetHost, randomBuf3, sizeof(randomBuf3)) == sizeof(randomBuf3));
Require(std::memcmp(randomBuf3, randomBuf2, sizeof(randomBuf3)) != 0);
const int secondRandom = open_nid_postfix("/dev/random", 0, 0);
Require(secondRandom >= 0);
Require(dup2_nid_postfix(dupRandom, secondRandom) == secondRandom);
unsigned char randomBuf4[16] = {};
Require(read_nid_postfix(secondRandom, randomBuf4, sizeof(randomBuf4)) == sizeof(randomBuf4));
Require(std::memcmp(randomBuf4, randomBuf3, sizeof(randomBuf4)) != 0);
const int invalidFd = FirstSocket - 100;
RequireFailure(dup2_nid_postfix(invalidFd, secondRandom), GuestEbadf);
unsigned char randomBuf5[16] = {};
Require(read_nid_postfix(secondRandom, randomBuf5, sizeof(randomBuf5)) == sizeof(randomBuf5));
for (const int descriptor : {random, dupRandom, pipe[1], secondRandom}) Require(close_nid_postfix(descriptor) == 0);
}
static void DuplicatesShareStatusFlags() {
constexpr int GetDescriptorFlags = 1;
constexpr int SetDescriptorFlags = 2;
constexpr int GetFlags = 3;
constexpr int SetFlags = 4;
constexpr int NonBlocking = 4;
constexpr int GuestEagain = 35;
int pipe[2];
int other[2];
Require(pipe_nid_postfix(pipe) == 0);
Require(pipe_nid_postfix(other) == 0);
Require(fcntl_nid_postfix(pipe[0], SetDescriptorFlags, 1) == 0);
const int duplicate = dup_nid_postfix(pipe[0]);
Require(duplicate >= 0);
Require(fcntl_nid_postfix(duplicate, GetDescriptorFlags) == 0);
Require(fcntl_nid_postfix(duplicate, SetFlags, NonBlocking) == 0);
Require(fcntl_nid_postfix(pipe[0], GetFlags) == NonBlocking);
Require(dup2_nid_postfix(pipe[0], other[0]) == other[0]);
Require(fcntl_nid_postfix(other[0], GetDescriptorFlags) == 0);
Require(fcntl_nid_postfix(other[0], GetFlags) == NonBlocking);
char buffer[4] = {};
RequireFailure(static_cast<int>(read_nid_postfix(other[0], buffer, sizeof(buffer))), GuestEagain);
Require(fcntl_nid_postfix(pipe[0], SetFlags, 0) == 0);
Require(fcntl_nid_postfix(duplicate, GetFlags) == 0);
Require(fcntl_nid_postfix(pipe[0], GetDescriptorFlags) == 1);
for (const int descriptor : {pipe[0], pipe[1], other[0], other[1], duplicate}) Require(close_nid_postfix(descriptor) == 0);
}
int main() {
DupOutlivesOriginal();
Dup2ReplacesTarget();
BadDescriptors();
SocketDuplicates();
RandomDeviceDuplicates();
DuplicatesShareStatusFlags();
}