mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
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>
218 lines
9.1 KiB
C++
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();
|
|
}
|