Files
AnyPS5/core/libs/tests/GuestKernelFcntl.cpp
T
orezraeyandClaude Opus 5.5 896bb41323 test(libkernel): check the empty ioctl request the console measured
The pioctl0 probe measured _IOC(IOC_IN, 'f', 127, 0), 0x8000667f, on a file,
a datagram socket, both pipe ends and a directory. The test used 0x80006601,
which the probe never sent, so it now uses the measured code.

That is the only empty input request measured. The malformed check applies to
every other one because FreeBSD's sys_ioctl tests only the direction bits and
the length, which the debt entry now says, along with why the i, r and f groups
keep ENOTTY on an INET socket.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-01 11:45:46 -03:00

554 lines
29 KiB
C++

#include "prx/libkernel/include/Api.hpp"
#include "prx/libkernel/File/include/File.hpp"
#include "prx/libc/include/Api.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <limits>
#include <stdexcept>
#include <string>
#include <thread>
#ifndef _WIN32
#include <fcntl.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
static void Require(bool value) { if (!value) std::abort(); }
static constexpr int DuplicateCommand = 0;
static constexpr int GetDescriptorFlags = 1;
static constexpr int SetDescriptorFlags = 2;
static constexpr int GetFlags = 3;
static constexpr int SetFlags = 4;
static constexpr int OldGetLock = 7;
static constexpr int OldSetLock = 8;
static constexpr int OldSetLockWait = 9;
static constexpr int Duplicate2Command = 10;
static constexpr int GetLock = 11;
static constexpr int SetLock = 12;
static constexpr int SetLockWait = 13;
static constexpr int ReadAhead = 15;
static constexpr int DarwinReadAhead = 16;
static constexpr int DuplicateCloseOnExec = 17;
static constexpr int Duplicate2CloseOnExec = 18;
static constexpr int ReadOnly = 0;
static constexpr int WriteOnly = 1;
static constexpr int ReadWrite = 2;
static constexpr int NonBlocking = 4;
static constexpr int Append = 8;
static constexpr int ReadAheadFlag = 0x200;
static constexpr int CreateReadWrite = 0x202;
static constexpr int CreateWriteAppend = 0x209;
static constexpr int OpenDirectory = 0x20000;
static constexpr std::int16_t ReadLock = 1;
static constexpr std::int16_t Unlock = 2;
static constexpr std::int16_t WriteLock = 3;
static constexpr std::int16_t FromStart = 0;
static constexpr std::int16_t FromCurrent = 1;
static constexpr std::int16_t FromEnd = 2;
static constexpr int GuestEbadf = 9;
static constexpr int GuestEfault = 14;
static constexpr int GuestEinval = 22;
static constexpr int GuestEnotty = 25;
static constexpr int GuestEagain = 35;
static constexpr int GuestEopnotsupp = 45;
static constexpr int GuestEaddrnotavail = 49;
static constexpr int GuestEoverflow = 84;
static constexpr std::uint64_t NonblockingRequest = 0x8004667e;
static constexpr std::uint64_t ReadableRequest = 0x4004667f;
static constexpr std::uint64_t WindowSizeRequest = 0x40087468;
static constexpr std::uint64_t AsyncRequest = 0x8004667d;
static constexpr std::uint64_t CloseOnExecRequest = 0x20006601;
static constexpr std::uint64_t KeepOnExecRequest = 0x20006602;
static constexpr std::uint64_t MalformedRequests[] = {0, 123, 0x5401, 0x5413, 0x40007468, 0x20087468, 0x00087468};
static constexpr std::uint64_t IntegerArgumentRequest = 0x20046601;
static constexpr std::uint64_t EmptyInputRequest = 0x8000667f;
static constexpr int SceEbadf = static_cast<int>(0x80020009u);
static constexpr int SceEinval = static_cast<int>(0x80020016u);
static constexpr int SceEnotsup = static_cast<int>(0x8002002du);
struct GuestLock {
std::int64_t start;
std::int64_t length;
std::int32_t pid;
std::int16_t type;
std::int16_t whence;
std::int32_t system;
};
static void RequireFailure(int result, int error) {
Require(result == -1);
Require(*__error_nid_postfix() == error);
}
template <typename TCall> static bool ThrowsWith(TCall call, const std::string& prefix) {
try {
call();
} catch (const std::runtime_error& error) {
return std::string(error.what()).rfind(prefix, 0) == 0;
}
return false;
}
static void RequireMalformedRequestsFail(int descriptor) {
char buffer[16] = {};
for (const std::uint64_t request : MalformedRequests) {
RequireFailure(ioctl_nid_postfix(descriptor, request, buffer), GuestEnotty);
RequireFailure(ioctl_nid_postfix(descriptor, request, nullptr), GuestEnotty);
}
}
static void SocketsKeepTheirPath() {
const int socket = socket_nid_postfix(2, 2, 0);
Require(socket >= 0);
Require(sceKernelFcntl(socket, GetFlags) == ReadWrite);
Require(sceKernelFcntl(socket, SetFlags, ReadWrite | NonBlocking) == 0);
Require(sceKernelFcntl(socket, GetFlags) == (ReadWrite | NonBlocking));
Require(sceKernelFcntl(socket, SetFlags, 0) == 0);
Require(sceKernelFcntl(socket, GetFlags) == ReadWrite);
GuestLock lock{};
Require(sceKernelFcntl(socket, GetLock, &lock) == SceEinval);
Require(sceKernelFcntl(socket, GetDescriptorFlags) == 0);
Require(sceKernelFcntl(socket, SetDescriptorFlags, 1) == 0);
Require(sceKernelFcntl(socket, GetDescriptorFlags) == 1);
const int duplicate = sceKernelFcntl(socket, DuplicateCommand, 20);
Require(duplicate >= 20);
Require(sceKernelFcntl(duplicate, GetDescriptorFlags) == 0);
Require(sceKernelFcntl(socket, GetDescriptorFlags) == 1);
Require(sceKernelFcntl(socket, SetFlags, ReadWrite | NonBlocking) == 0);
Require(sceKernelFcntl(duplicate, GetFlags) == (ReadWrite | NonBlocking));
Require(sceKernelFcntl(duplicate, SetFlags, 0) == 0);
Require(sceKernelFcntl(socket, GetFlags) == ReadWrite);
Require(sceKernelFcntl(socket, DuplicateCloseOnExec, duplicate + 1) == SceEnotsup);
RequireFailure(fcntl_nid_postfix(socket, DuplicateCloseOnExec, duplicate + 1), GuestEopnotsupp);
const int closing = sceKernelFcntl(socket, DuplicateCommand, duplicate + 1);
Require(closing >= duplicate + 1);
Require(sceKernelFcntl(closing, SetDescriptorFlags, 1) == 0);
Require(sceKernelFcntl(closing, GetDescriptorFlags) == 1);
Require(sceKernelFcntl(socket, SetDescriptorFlags, 0) == 0);
Require(sceKernelFcntl(socket, GetDescriptorFlags) == 0);
Require(fcntl_nid_postfix(socket, Duplicate2Command, closing) == closing);
Require(sceKernelFcntl(closing, GetDescriptorFlags) == 0);
for (const int target : {closing, socket, -1, -2}) {
const int copy = sceKernelFcntl(socket, Duplicate2CloseOnExec, target);
Require(copy >= 0 && copy != target && copy != socket && copy != closing);
Require(sceKernelFcntl(copy, GetDescriptorFlags) == 0 && sceKernelFcntl(copy, GetFlags) == ReadWrite);
Require(close_nid_postfix(copy) == 0);
const int posix = fcntl_nid_postfix(socket, Duplicate2CloseOnExec, target);
Require(posix >= 0 && posix != target && posix != socket && close_nid_postfix(posix) == 0);
}
Require(sceKernelFcntl(closing, GetDescriptorFlags) == 0);
Require(sceKernelFcntl(socket, GetDescriptorFlags) == 0);
Require(sceKernelFcntl(socket, SetDescriptorFlags, 1) == 0);
Require(sceKernelFcntl(socket, Duplicate2Command, socket) == socket);
Require(sceKernelFcntl(socket, GetDescriptorFlags) == 1);
Require(sceKernelFcntl(socket, SetDescriptorFlags, 0) == 0);
int pipe[2] = {-1, -1};
Require(pipe_nid_postfix(pipe) == 0);
Require(fcntl_nid_postfix(pipe[0], SetDescriptorFlags, 1) == 0);
Require(fcntl_nid_postfix(socket, Duplicate2Command, pipe[0]) == pipe[0]);
Require(sceKernelFcntl(pipe[0], GetFlags) == ReadWrite);
Require(sceKernelFcntl(pipe[0], GetDescriptorFlags) == 0);
const int pipeCopy = sceKernelFcntl(socket, Duplicate2CloseOnExec, pipe[1]);
Require(pipeCopy >= 0 && pipeCopy != pipe[1] && sceKernelFcntl(pipeCopy, GetDescriptorFlags) == 0 && close_nid_postfix(pipeCopy) == 0);
Require(sceKernelFcntl(pipe[1], GetDescriptorFlags) == 0);
for (const int end : pipe) {
const int copy = fcntl_nid_postfix(end, Duplicate2CloseOnExec, 300);
Require(copy >= 0 && copy != end && copy != 300 && fcntl_nid_postfix(copy, GetDescriptorFlags) == 0 && close_nid_postfix(copy) == 0);
}
for (const int end : pipe) RequireFailure(fcntl_nid_postfix(end, DuplicateCloseOnExec, 0), GuestEopnotsupp);
Require(close_nid_postfix(pipe[0]) == 0 && close_nid_postfix(pipe[1]) == 0);
const int shared = shm_open_nid_postfix(reinterpret_cast<const char*>(1), ReadWrite, 0600);
Require(shared >= 0);
RequireFailure(fcntl_nid_postfix(shared, DuplicateCloseOnExec, 0), GuestEopnotsupp);
Require(close_nid_postfix(shared) == 0);
const int random = sceKernelOpen("/dev/urandom", ReadOnly, 0);
Require(random >= 0);
Require(fcntl_nid_postfix(socket, Duplicate2Command, random) == random);
Require(sceKernelFcntl(random, GetFlags) == ReadWrite);
Require(close_nid_postfix(random) == 0);
const int file = sceKernelOpen("kernel_fcntl_socket_dup2.bin", CreateReadWrite, 0644);
Require(file == random);
char byte = 0;
Require(sceKernelRead(file, &byte, 1) == 0);
const int fileCopy = sceKernelFcntl(file, Duplicate2CloseOnExec, 300);
Require(fileCopy >= 0 && fileCopy != file && fileCopy != 300);
Require(sceKernelFcntl(fileCopy, GetDescriptorFlags) == 0 && sceKernelFcntl(fileCopy, GetFlags) == ReadWrite);
Require(sceKernelLseek(file, 1, 0) == 1 && sceKernelLseek(fileCopy, 0, 1) == 1);
Require(close_nid_postfix(fileCopy) == 0);
RequireFailure(fcntl_nid_postfix(-1, Duplicate2CloseOnExec, 300), GuestEbadf);
RequireFailure(fcntl_nid_postfix(9999, Duplicate2CloseOnExec, 300), GuestEbadf);
Require(sceKernelFcntl(9999, Duplicate2CloseOnExec, 300) == SceEbadf);
Require(close_nid_postfix(file) == 0);
std::remove("kernel_fcntl_socket_dup2.bin");
RequireFailure(fcntl_nid_postfix(socket, DuplicateCommand, -1), GuestEinval);
RequireFailure(fcntl_nid_postfix(socket, DuplicateCloseOnExec, -1), GuestEopnotsupp);
RequireFailure(fcntl_nid_postfix(socket, Duplicate2Command, -1), GuestEbadf);
Require(sceKernelFcntl(socket, DuplicateCommand, -1) == SceEinval);
Require(sceKernelFcntl(socket, DuplicateCloseOnExec, -1) == SceEnotsup);
Require(sceKernelFcntl(socket, Duplicate2Command, -1) == SceEbadf);
Require(sceKernelFcntl(socket, SetFlags, 0x8000) == SceEnotsup);
Require(sceKernelFcntl(socket, GetFlags) == ReadWrite);
Require(sceKernelFcntl(socket, 5) == SceEinval);
Require(sceKernelFcntl(socket + 4096, GetFlags) == SceEbadf);
int queued = -1;
Require(ioctl_nid_postfix(socket, ReadableRequest, &queued) == 0 && queued == 0);
RequireMalformedRequestsFail(socket);
RequireFailure(ioctl_nid_postfix(socket, IntegerArgumentRequest, nullptr), GuestEfault);
RequireFailure(ioctl_nid_postfix(socket, EmptyInputRequest, nullptr), GuestEnotty);
char windowSize[8] = {};
RequireFailure(ioctl_nid_postfix(socket, WindowSizeRequest, windowSize), GuestEaddrnotavail);
for (const int descriptor : {closing, duplicate, socket}) Require(close_nid_postfix(descriptor) == 0);
Require(sceKernelFcntl(socket, GetFlags) == SceEbadf);
Require(sceKernelFcntl(-1, GetFlags) == SceEbadf);
Require(sceKernelFcntl(12345, GetFlags) == SceEbadf);
const int named = __sys_socketex_nid_postfix("probe", 2, 1, 0);
Require(named >= 0 && sceKernelFcntl(named, GetFlags) == ReadWrite && close_nid_postfix(named) == 0);
}
static void FileFlagsRoundTrip() {
std::remove("kernel_fcntl_probe.bin");
const int file = sceKernelOpen("kernel_fcntl_probe.bin", CreateReadWrite, 0644);
Require(file >= 0);
Require(sceKernelFcntl(file, GetFlags) == ReadWrite);
Require(fcntl_nid_postfix(file, SetFlags, ReadWrite | NonBlocking | 0x8000) == 0);
Require(fcntl_nid_postfix(file, GetFlags) == (ReadWrite | NonBlocking));
Require(fcntl_nid_postfix(file, SetFlags, 0) == 0);
Require(fcntl_nid_postfix(file, GetFlags) == ReadWrite);
Require(fcntl_nid_postfix(file, GetDescriptorFlags) == 0);
Require(fcntl_nid_postfix(file, SetDescriptorFlags, 1) == 0);
Require(fcntl_nid_postfix(file, GetDescriptorFlags) == 1);
Require(sceKernelFcntl(file, SetDescriptorFlags, 0) == 0);
Require(sceKernelFcntl(file, GetDescriptorFlags) == 0);
Require(ioctl_nid_postfix(file, CloseOnExecRequest, nullptr) == 0);
Require(fcntl_nid_postfix(file, GetDescriptorFlags) == 1);
Require(ioctl_nid_postfix(file, KeepOnExecRequest, nullptr) == 0);
Require(fcntl_nid_postfix(file, GetDescriptorFlags) == 0);
Require(fcntl_nid_postfix(file, DarwinReadAhead, 1) == 0);
Require(fcntl_nid_postfix(file, GetFlags) == (ReadWrite | ReadAheadFlag));
Require(fcntl_nid_postfix(file, ReadAhead, -1) == 0);
Require(fcntl_nid_postfix(file, GetFlags) == ReadWrite);
Require(sceKernelFcntl(file, ReadAhead, 4096) == 0);
Require(fcntl_nid_postfix(file, GetFlags) == (ReadWrite | ReadAheadFlag));
Require(sceKernelFcntl(file, ReadAhead, -1) == 0);
Require(sceKernelFcntl(file, GetFlags) == ReadWrite);
Require(fcntl_nid_postfix(file, DarwinReadAhead, 1) == 0);
const int duplicate = fcntl_nid_postfix(file, DuplicateCommand, 20);
Require(duplicate >= 20);
Require(fcntl_nid_postfix(duplicate, GetDescriptorFlags) == 0);
Require(fcntl_nid_postfix(file, SetFlags, NonBlocking) == 0);
Require(fcntl_nid_postfix(duplicate, GetFlags) == (ReadWrite | NonBlocking));
for (const int minimum : {0, duplicate, 200, -1}) {
RequireFailure(fcntl_nid_postfix(file, DuplicateCloseOnExec, minimum), GuestEopnotsupp);
Require(sceKernelFcntl(file, DuplicateCloseOnExec, minimum) == SceEnotsup);
}
RequireFailure(fcntl_nid_postfix(-1, DuplicateCloseOnExec, 0), GuestEbadf);
RequireFailure(fcntl_nid_postfix(9999, DuplicateCloseOnExec, 0), GuestEbadf);
Require(sceKernelFcntl(-1, DuplicateCloseOnExec, 0) == SceEbadf);
Require(fcntl_nid_postfix(file, GetDescriptorFlags) == 0);
Require(write_nid_postfix(file, "abcde", 5) == 5);
Require(sceKernelLseek(file, 2, 0) == 2);
int readable = -1;
Require(ioctl_nid_postfix(file, ReadableRequest, &readable) == 0 && readable == 3);
char size[8] = {};
RequireFailure(ioctl_nid_postfix(file, WindowSizeRequest, size), GuestEnotty);
RequireFailure(ioctl_nid_postfix(file, EmptyInputRequest, nullptr), GuestEnotty);
RequireFailure(ioctl_nid_postfix(file, EmptyInputRequest, size), GuestEnotty);
RequireMalformedRequestsFail(file);
RequireFailure(ioctl_nid_postfix(file, NonblockingRequest, nullptr), GuestEfault);
Require(ThrowsWith([file] { int on = 1; ioctl_nid_postfix(file, AsyncRequest, &on); }, "ioctl: request 0x8004667D on a file is not implemented"));
RequireFailure(fcntl_nid_postfix(file, DuplicateCommand, -1), GuestEinval);
Require(sceKernelFcntl(file, DuplicateCommand, -1) == SceEinval);
RequireFailure(fcntl_nid_postfix(file, 99), GuestEinval);
RequireFailure(fcntl_nid_postfix(file, GetLock, nullptr), GuestEfault);
GuestLock lock{0, 0, 0, 3, 0, 0};
Require(fcntl_nid_postfix(file, SetLock, &lock) == 0);
GuestLock probe{0, 0, 0, 1, 0, 0};
Require(fcntl_nid_postfix(file, GetLock, &probe) == 0 && probe.type == 2);
static GuestLock imageProbe{0, 0, 0, 1, 0, 0};
Require(_fcntl_nid_postfix(file, GetLock, &imageProbe) == 0 && imageProbe.type == 2);
Require(_fcntl_nid_postfix(file, SetDescriptorFlags, 1) == 0 && _fcntl_nid_postfix(file, GetDescriptorFlags) == 1);
Require(_fcntl_nid_postfix(file, SetDescriptorFlags, 0) == 0);
lock.type = 2;
Require(fcntl_nid_postfix(file, SetLock, &lock) == 0);
lock.type = 7;
RequireFailure(fcntl_nid_postfix(file, SetLock, &lock), GuestEinval);
for (const int descriptor : {duplicate, file}) Require(close_nid_postfix(descriptor) == 0);
RequireFailure(fcntl_nid_postfix(file, GetDescriptorFlags), GuestEbadf);
RequireFailure(fcntl_nid_postfix(file, DuplicateCommand, 0), GuestEbadf);
RequireFailure(ioctl_nid_postfix(file, ReadableRequest, &readable), GuestEbadf);
RequireMalformedRequestsFail(file);
RequireMalformedRequestsFail(-1);
Require(sceKernelFcntl(file, GetFlags) == SceEbadf);
const int appending = sceKernelOpen("kernel_fcntl_probe.bin", CreateWriteAppend, 0644);
Require(appending >= 0);
Require(fcntl_nid_postfix(appending, GetFlags) == (WriteOnly | Append));
Require(fcntl_nid_postfix(appending, SetFlags, Append | NonBlocking) == 0);
Require(fcntl_nid_postfix(appending, GetFlags) == (WriteOnly | Append | NonBlocking));
Require(write_nid_postfix(appending, "ab", 2) == 2);
Require(fcntl_nid_postfix(appending, SetFlags, 0) == 0);
Require(fcntl_nid_postfix(appending, GetFlags) == WriteOnly);
Require(sceKernelLseek(appending, 0, FromStart) == 0);
Require(write_nid_postfix(appending, "X", 1) == 1);
const int sharing = fcntl_nid_postfix(appending, DuplicateCommand, 0);
Require(sharing >= 0);
Require(fcntl_nid_postfix(sharing, SetFlags, Append) == 0);
Require(fcntl_nid_postfix(appending, GetFlags) == (WriteOnly | Append));
Require(sceKernelLseek(appending, 0, FromStart) == 0);
Require(write_nid_postfix(appending, "c", 1) == 1);
Require(fcntl_nid_postfix(sharing, SetFlags, 0) == 0);
Require(sceKernelLseek(appending, 0, FromStart) == 0);
Require(write_nid_postfix(appending, "Y", 1) == 1);
Require(close_nid_postfix(sharing) == 0);
const int reading = sceKernelOpen("kernel_fcntl_probe.bin", ReadOnly, 0);
Require(reading >= 0);
char written[16]{};
Require(sceKernelRead(reading, written, sizeof(written)) == 8 && std::memcmp(written, "Ybcdeabc", 8) == 0);
Require(sceKernelClose(reading) == 0);
Require(fcntl_nid_postfix(appending, SetFlags, Append | 0x80) == 0 && fcntl_nid_postfix(appending, GetFlags) == (WriteOnly | Append | 0x80));
Require(sceKernelClose(appending) == 0);
std::remove("kernel_fcntl_probe.bin");
}
static bool NoConflict(int fd, GuestLock request) {
const GuestLock asked = request;
if (fcntl_nid_postfix(fd, GetLock, &request) != 0) return false;
return request.type == Unlock && request.start == asked.start && request.length == asked.length && request.pid == asked.pid && request.whence == asked.whence && request.system == asked.system;
}
static void RecordLocksBelongToTheProcess() {
const int file = sceKernelOpen("kernel_fcntl_locks.bin", CreateReadWrite, 0644);
Require(file >= 0);
Require(write_nid_postfix(file, "0123456789", 10) == 10);
GuestLock whole{0, 0, 0, WriteLock, FromStart, 0};
Require(fcntl_nid_postfix(file, SetLock, &whole) == 0);
Require(NoConflict(file, {4, 2, 0, WriteLock, FromStart, 77}));
GuestLock middle{2, 3, 0, ReadLock, FromStart, 0};
Require(fcntl_nid_postfix(file, SetLock, &middle) == 0);
GuestLock tail{5, 0, 0, Unlock, FromStart, 0};
Require(fcntl_nid_postfix(file, SetLock, &tail) == 0);
GuestLock merged{0, 10, 0, WriteLock, FromStart, 0};
Require(fcntl_nid_postfix(file, SetLockWait, &merged) == 0);
Require(NoConflict(file, {0, 0, 0, WriteLock, FromStart, 0}));
const int other = sceKernelOpen("kernel_fcntl_locks.bin", ReadWrite, 0);
Require(other >= 0);
GuestLock same{0, 10, 0, WriteLock, FromStart, 0};
Require(fcntl_nid_postfix(other, SetLock, &same) == 0);
Require(fcntl_nid_postfix(other, SetLockWait, &same) == 0);
Require(NoConflict(other, {0, 0, 0, WriteLock, FromStart, 0}));
int threadSet = -1;
int threadWait = -1;
bool threadSeesNoConflict = false;
std::thread thread([&] {
GuestLock request{3, 4, 0, WriteLock, FromStart, 0};
threadSet = fcntl_nid_postfix(other, SetLock, &request);
threadWait = fcntl_nid_postfix(file, SetLockWait, &request);
threadSeesNoConflict = NoConflict(file, {0, 0, 0, WriteLock, FromStart, 0});
});
thread.join();
Require(threadSet == 0 && threadWait == 0 && threadSeesNoConflict);
Require(sceKernelLseek(file, 6, 0) == 6);
GuestLock current{-2, 0, 0, ReadLock, FromCurrent, 5};
Require(fcntl_nid_postfix(file, GetLock, &current) == 0);
Require(current.type == Unlock && current.start == 4 && current.whence == FromCurrent && current.length == 0 && current.system == 5);
GuestLock end{-3, 0, 0, WriteLock, FromEnd, 0};
Require(fcntl_nid_postfix(file, SetLock, &end) == 0);
GuestLock backwards{5, -5, 0, ReadLock, FromStart, 0};
Require(fcntl_nid_postfix(file, SetLock, &backwards) == 0);
unsigned char old[32];
std::memset(old, 0xa5, sizeof(old));
GuestLock oldLock{0, 1, 0, WriteLock, FromStart, 0};
std::memcpy(old, &oldLock, 24);
Require(fcntl_nid_postfix(file, OldSetLock, old) == 0);
Require(fcntl_nid_postfix(file, OldSetLockWait, old) == 0);
Require(fcntl_nid_postfix(file, OldGetLock, old) == 0);
std::memcpy(&oldLock, old, 24);
Require(oldLock.type == Unlock && oldLock.start == 0 && oldLock.length == 1);
for (std::size_t index = 24; index < sizeof(old); ++index) Require(old[index] == 0xa5);
GuestLock badType{0, 0, 0, 0, FromStart, 0};
RequireFailure(fcntl_nid_postfix(file, SetLock, &badType), GuestEinval);
badType.type = 4;
RequireFailure(fcntl_nid_postfix(file, SetLock, &badType), GuestEinval);
badType.type = 7;
RequireFailure(fcntl_nid_postfix(file, GetLock, &badType), GuestEinval);
GuestLock negative{-1, 0, 0, WriteLock, FromStart, 0};
RequireFailure(fcntl_nid_postfix(file, SetLock, &negative), GuestEinval);
GuestLock beforeStart{0, -1, 0, WriteLock, FromStart, 0};
RequireFailure(fcntl_nid_postfix(file, SetLock, &beforeStart), GuestEinval);
GuestLock pastStart{5, -6, 0, WriteLock, FromStart, 0};
RequireFailure(fcntl_nid_postfix(file, SetLock, &pastStart), GuestEinval);
GuestLock unknownWhence{0, 0, 0, WriteLock, 3, 0};
RequireFailure(fcntl_nid_postfix(file, SetLock, &unknownWhence), GuestEinval);
Require(sceKernelFcntl(file, SetLock, &unknownWhence) == SceEinval);
const int reader = sceKernelOpen("kernel_fcntl_locks.bin", ReadOnly, 0);
Require(reader >= 0);
GuestLock write{0, 0, 0, WriteLock, FromStart, 0};
RequireFailure(fcntl_nid_postfix(reader, SetLock, &write), GuestEbadf);
RequireFailure(fcntl_nid_postfix(reader, SetLockWait, &write), GuestEbadf);
Require(sceKernelFcntl(reader, SetLock, &write) == SceEbadf);
Require(NoConflict(reader, write));
GuestLock read{0, 0, 0, ReadLock, FromStart, 0};
Require(fcntl_nid_postfix(reader, SetLock, &read) == 0);
const int writer = sceKernelOpen("kernel_fcntl_locks.bin", WriteOnly, 0);
Require(writer >= 0);
RequireFailure(fcntl_nid_postfix(writer, SetLock, &read), GuestEbadf);
Require(fcntl_nid_postfix(writer, SetLock, &write) == 0);
GuestLock release{0, 0, 0, Unlock, FromStart, 0};
Require(fcntl_nid_postfix(writer, SetLock, &release) == 0);
Require(fcntl_nid_postfix(reader, SetLock, &release) == 0);
Require(NoConflict(file, {0, 0, 0, Unlock, FromStart, 0}));
Require(NoConflict(reader, {2, 3, 0, Unlock, FromStart, 9}));
Require(sceKernelLseek(file, 7, 0) == 7);
GuestLock currentUnlock{-3, 2, 0, Unlock, FromCurrent, 0};
Require(fcntl_nid_postfix(file, GetLock, &currentUnlock) == 0);
Require(currentUnlock.type == Unlock && currentUnlock.start == 4 && currentUnlock.length == 2 && currentUnlock.whence == FromCurrent);
#ifdef _WIN32
GuestLock tooLong{std::numeric_limits<std::int64_t>::max(), 2, 0, WriteLock, FromStart, 0};
RequireFailure(fcntl_nid_postfix(file, SetLock, &tooLong), GuestEoverflow);
RequireFailure(fcntl_nid_postfix(file, GetLock, &tooLong), GuestEoverflow);
GuestLock pastEnd{std::numeric_limits<std::int64_t>::max(), 0, 0, WriteLock, FromEnd, 0};
RequireFailure(fcntl_nid_postfix(file, SetLock, &pastEnd), GuestEoverflow);
GuestLock pastCurrent{std::numeric_limits<std::int64_t>::max(), 0, 0, ReadLock, FromCurrent, 0};
Require(fcntl_nid_postfix(reader, SetLock, &pastCurrent) == 0);
pastCurrent.type = WriteLock;
Require(sceKernelLseek(reader, 1, 0) == 1);
RequireFailure(fcntl_nid_postfix(reader, SetLock, &pastCurrent), GuestEoverflow);
GuestLock writeBeforeWhence{0, 0, 0, WriteLock, 3, 0};
RequireFailure(fcntl_nid_postfix(reader, SetLock, &writeBeforeWhence), GuestEbadf);
RequireFailure(fcntl_nid_postfix(reader, GetLock, &writeBeforeWhence), GuestEinval);
std::filesystem::create_directory("kernel_fcntl_locks_dir");
const int directory = sceKernelOpen("kernel_fcntl_locks_dir", OpenDirectory, 0);
Require(directory >= 0);
Require(fcntl_nid_postfix(directory, SetLock, &read) == 0);
RequireFailure(fcntl_nid_postfix(directory, SetLock, &write), GuestEbadf);
Require(NoConflict(directory, {0, 0, 0, WriteLock, FromCurrent, 0}));
GuestLock directoryEnd{0, 0, 0, ReadLock, FromEnd, 0};
Require(ThrowsWith([directory, &directoryEnd] { fcntl_nid_postfix(directory, SetLock, &directoryEnd); }, "fcntl: a record lock from the end on a directory is not implemented"));
Require(close_nid_postfix(directory) == 0);
std::filesystem::remove("kernel_fcntl_locks_dir");
#endif
for (const int descriptor : {writer, reader, other}) Require(close_nid_postfix(descriptor) == 0);
Require(fcntl_nid_postfix(file, SetLock, &whole) == 0);
Require(NoConflict(file, {0, 0, 0, WriteLock, FromStart, 0}));
Require(close_nid_postfix(file) == 0);
RequireFailure(fcntl_nid_postfix(file, SetLock, &whole), GuestEbadf);
RequireFailure(fcntl_nid_postfix(file, GetLock, &whole), GuestEbadf);
std::remove("kernel_fcntl_locks.bin");
}
static void PipeFlagsRoundTrip() {
int pipe[2];
Require(pipe_nid_postfix(pipe) == 0);
Require(fcntl_nid_postfix(pipe[0], GetFlags) == ReadWrite);
Require(fcntl_nid_postfix(pipe[1], GetFlags) == ReadWrite);
const int duplicate = fcntl_nid_postfix(pipe[1], DuplicateCommand, 20);
Require(duplicate >= 20 && fcntl_nid_postfix(duplicate, GetFlags) == ReadWrite && close_nid_postfix(duplicate) == 0);
Require(fcntl_nid_postfix(pipe[1], SetDescriptorFlags, 1) == 0);
Require(fcntl_nid_postfix(pipe[1], GetDescriptorFlags) == 1);
Require(fcntl_nid_postfix(pipe[0], GetDescriptorFlags) == 0);
Require(ioctl_nid_postfix(pipe[0], CloseOnExecRequest, nullptr) == 0);
Require(fcntl_nid_postfix(pipe[0], GetDescriptorFlags) == 1);
Require(ioctl_nid_postfix(pipe[1], KeepOnExecRequest, nullptr) == 0);
Require(fcntl_nid_postfix(pipe[1], GetDescriptorFlags) == 0);
Require(fcntl_nid_postfix(pipe[0], SetFlags, fcntl_nid_postfix(pipe[0], GetFlags) | NonBlocking) == 0);
Require(fcntl_nid_postfix(pipe[0], GetFlags) == (ReadWrite | NonBlocking));
char buffer[8] = {};
RequireFailure(static_cast<int>(read_nid_postfix(pipe[0], buffer, sizeof(buffer))), GuestEagain);
Require(write_nid_postfix(pipe[1], "abc", 3) == 3);
int readable = -1;
Require(ioctl_nid_postfix(pipe[0], ReadableRequest, &readable) == 0 && readable == 3);
Require(ioctl_nid_postfix(pipe[1], ReadableRequest, &readable) == 0 && readable == 0);
Require(read_nid_postfix(pipe[0], buffer, sizeof(buffer)) == 3);
RequireFailure(static_cast<int>(read_nid_postfix(pipe[0], buffer, sizeof(buffer))), GuestEagain);
int off = 0;
Require(ioctl_nid_postfix(pipe[0], NonblockingRequest, &off) == 0);
Require(fcntl_nid_postfix(pipe[0], GetFlags) == ReadWrite);
int on = 1;
Require(ioctl_nid_postfix(pipe[0], NonblockingRequest, &on) == 0);
Require(fcntl_nid_postfix(pipe[0], GetFlags) == (ReadWrite | NonBlocking));
Require(fcntl_nid_postfix(pipe[1], SetFlags, NonBlocking) == 0);
char block[1024] = {};
std::int64_t written = 0;
std::int64_t result = 0;
while (written < (1 << 22) && (result = write_nid_postfix(pipe[1], block, sizeof(block))) > 0) written += result;
Require(written > 0);
RequireFailure(static_cast<int>(result), GuestEagain);
RequireFailure(fcntl_nid_postfix(pipe[0], DarwinReadAhead, 1), GuestEbadf);
GuestLock lock{0, 0, 0, 3, 0, 0};
RequireFailure(fcntl_nid_postfix(pipe[1], SetLock, &lock), GuestEbadf);
char size[8] = {};
RequireFailure(ioctl_nid_postfix(pipe[0], WindowSizeRequest, size), GuestEnotty);
RequireFailure(ioctl_nid_postfix(pipe[0], EmptyInputRequest, nullptr), GuestEnotty);
RequireMalformedRequestsFail(pipe[0]);
Require(ThrowsWith([&pipe] { fcntl_nid_postfix(pipe[0], SetFlags, NonBlocking | 0x40); }, "fcntl: O_ASYNC on a pipe is not implemented"));
for (const int descriptor : pipe) Require(close_nid_postfix(descriptor) == 0);
}
#ifndef _WIN32
static void HostSocketFlags() {
int sockets[2] = {-1, -1};
Require(::socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0);
Require(fcntl_nid_postfix(sockets[0], GetFlags) == ReadWrite);
Require(fcntl_nid_postfix(sockets[0], SetFlags, NonBlocking) == 0);
Require(fcntl_nid_postfix(sockets[0], GetFlags) == (ReadWrite | NonBlocking));
Require((::fcntl(sockets[0], F_GETFL) & O_NONBLOCK) != 0);
Require(fcntl_nid_postfix(sockets[0], SetDescriptorFlags, 1) == 0);
Require(fcntl_nid_postfix(sockets[0], GetDescriptorFlags) == 1);
Require(::write(sockets[1], "abc", 3) == 3);
int available = 0;
Require(ioctl_nid_postfix(sockets[0], ReadableRequest, &available) == 0 && available == 3);
GuestLock lock{0, 0, 0, WriteLock, FromStart, 0};
RequireFailure(fcntl_nid_postfix(sockets[0], SetLock, &lock), GuestEbadf);
RequireFailure(fcntl_nid_postfix(sockets[0], DarwinReadAhead, 1), GuestEbadf);
Require(ThrowsWith([&sockets] { fcntl_nid_postfix(sockets[0], SetFlags, NonBlocking | 0x40); }, "fcntl: O_ASYNC on a socket is not implemented"));
for (const int socket : sockets) Require(close_nid_postfix(socket) == 0);
}
#endif
static void StandardInputFlags() {
const int flags = fcntl_nid_postfix(0, GetFlags);
Require(flags >= 0);
Require((flags & 3) != WriteOnly);
Require(fcntl_nid_postfix(0, SetFlags, fcntl_nid_postfix(0, GetFlags) & ~NonBlocking) == 0);
Require(fcntl_nid_postfix(0, GetFlags) == (flags & ~NonBlocking));
Require(fcntl_nid_postfix(0, SetDescriptorFlags, 1) == 0);
Require(fcntl_nid_postfix(0, GetDescriptorFlags) == 1);
Require(fcntl_nid_postfix(0, SetDescriptorFlags, 0) == 0);
Require(fcntl_nid_postfix(0, GetDescriptorFlags) == 0);
}
int main() {
SocketsKeepTheirPath();
FileFlagsRoundTrip();
RecordLocksBelongToTheProcess();
PipeFlagsRoundTrip();
#ifndef _WIN32
HostSocketFlags();
#endif
StandardInputFlags();
return 0;
}