Merge pull request #3840 from ahir061/feat/libkernel-fcntl-files

feat(libkernel): support fcntl and ioctl on non-socket descriptors
This commit is contained in:
Dean 2026-10-12 23:53:24 +13:00 committed by GitHub
commit 5f3b94cb19
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 839 additions and 27 deletions

View File

@ -3,6 +3,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/Apr/src/Apr.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Equeue/Equeue.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Equeue/src/EventAccessors.cpp
${CMAKE_CURRENT_SOURCE_DIR}/File/src/DescriptorControl.cpp
${CMAKE_CURRENT_SOURCE_DIR}/File/src/DirectoryDescriptor.cpp
${CMAKE_CURRENT_SOURCE_DIR}/File/src/FileLock.cpp
${CMAKE_CURRENT_SOURCE_DIR}/File/src/NativeStat.cpp

View File

@ -0,0 +1,20 @@
#ifndef CORE_LIBS_PRX_LIBKERNEL_FILE_DESCRIPTORCONTROL_HPP
#define CORE_LIBS_PRX_LIBKERNEL_FILE_DESCRIPTORCONTROL_HPP
#include <cstdint>
namespace File {
enum class FcntlArgument { None, Int, Pointer };
FcntlArgument FcntlArgumentOf(int command);
int Fcntl(int fd, int command, std::intptr_t argument);
int Ioctl(int fd, std::uint64_t request, void* argument);
int DuplicateFrom(int fd, int start);
void RememberDescriptor(int fd, int openFlags);
void ShareDescriptor(int from, int to);
void ForgetDescriptor(int fd);
}
#endif

View File

@ -0,0 +1,510 @@
#include "prx/libkernel/File/include/DescriptorControl.hpp"
#include "prx/libkernel/File/include/DirectoryDescriptor.hpp"
#include "prx/libkernel/File/include/File.hpp"
#include "prx/libkernel/File/include/RandomDevice.hpp"
#include "prx/libc/include/GuestArena.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cerrno>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <stdexcept>
#include <string>
#ifdef _WIN32
#include <io.h>
#include <windows.h>
#include <winternl.h>
#else
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
extern "C" int* APS5_VABI __error_nid_postfix();
extern "C" int APS5_VABI dup2_nid_postfix(int from, int to);
#ifdef _WIN32
extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handler(_invalid_parameter_handler);
#endif
namespace {
constexpr int GuestEintr = 4;
constexpr int GuestEbadf = 9;
constexpr int GuestEdeadlk = 11;
constexpr int GuestEfault = 14;
constexpr int GuestEinval = 22;
constexpr int GuestEnotty = 25;
constexpr int GuestEagain = 35;
constexpr int GuestEnolck = 77;
constexpr int GuestEoverflow = 84;
constexpr int DuplicateCommand = 0;
constexpr int GetDescriptorFlags = 1;
constexpr int SetDescriptorFlags = 2;
constexpr int GetStatusFlags = 3;
constexpr int SetStatusFlags = 4;
constexpr int GetOwner = 5;
constexpr int SetOwner = 6;
constexpr int OldGetLock = 7;
constexpr int OldSetLock = 8;
constexpr int OldSetLockWait = 9;
constexpr int DuplicateToCommand = 10;
constexpr int GetLock = 11;
constexpr int SetLock = 12;
constexpr int SetLockWait = 13;
constexpr int SetLockRemote = 14;
constexpr int ReadAhead = 15;
constexpr int DarwinReadAhead = 16;
constexpr int DuplicateCloseOnExec = 17;
constexpr int DuplicateToCloseOnExec = 18;
constexpr int CloseOnExec = 1;
constexpr int OpenCloseOnExec = 0x00100000;
constexpr int ReadOnly = 0;
constexpr int WriteOnly = 1;
constexpr int ReadWrite = 2;
constexpr int Nonblocking = 0x0004;
constexpr int Append = 0x0008;
constexpr int Asynchronous = 0x0040;
constexpr int Synchronous = 0x0080;
constexpr int ReadAheadFlag = 0x0200;
constexpr int Direct = 0x00010000;
constexpr int OpenStatusFlags = Nonblocking | Append | Asynchronous | Synchronous | Direct;
constexpr int SettableStatusFlags = OpenStatusFlags | ReadAheadFlag;
constexpr std::int16_t ReadLock = 1;
constexpr std::int16_t Unlock = 2;
constexpr std::int16_t WriteLock = 3;
constexpr std::int16_t FromCurrent = 1;
constexpr std::uint32_t NonblockingRequest = 0x8004667e;
constexpr std::uint32_t ReadableRequest = 0x4004667f;
constexpr std::uint32_t RequestIn = 0x80000000u;
constexpr std::uint32_t TerminalGroup = 't';
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_assert(sizeof(GuestLock) == 32);
static_assert(offsetof(GuestLock, type) == 20);
constexpr std::size_t OldGuestLockSize = 24;
enum class Kind { File, Directory, Pipe, Device, RandomDevice };
struct Entry {
bool closeOnExec = false;
std::shared_ptr<int> status;
};
std::mutex g_mutex;
std::map<int, Entry> g_entries;
int Fail(int error) {
*__error_nid_postfix() = error;
return -1;
}
const char* KindName(Kind kind) {
switch (kind) {
case Kind::File: return "file";
case Kind::Directory: return "directory";
case Kind::Pipe: return "pipe";
case Kind::Device: return "character device";
case Kind::RandomDevice: return "random device";
}
return "descriptor";
}
[[noreturn]] void Unsupported(const char* function, const std::string& what, Kind kind, int fd) {
throw std::runtime_error(std::string(function) + ": " + what + " on a " + KindName(kind) + " is not implemented, fd=" + std::to_string(fd));
}
#ifdef _WIN32
void IgnoreInvalidParameter(const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, std::uintptr_t) {}
HANDLE NativeHandle(int fd) {
const auto previous = _set_thread_local_invalid_parameter_handler(IgnoreInvalidParameter);
const auto handle = ::_get_osfhandle(fd);
_set_thread_local_invalid_parameter_handler(previous);
return reinterpret_cast<HANDLE>(handle);
}
std::optional<Kind> Classify(int fd, const char* function) {
if (fd < 0 || !DescriptorIsOpen_nid_no_patch(fd)) return std::nullopt;
if (File::DirectoryDescriptorPath(fd)) return Kind::Directory;
if (File::IsRandomDevice(fd)) return Kind::RandomDevice;
switch (::GetFileType(NativeHandle(fd))) {
case FILE_TYPE_DISK: return Kind::File;
case FILE_TYPE_PIPE: return Kind::Pipe;
case FILE_TYPE_CHAR: return Kind::Device;
default: throw std::runtime_error(std::string(function) + ": descriptor " + std::to_string(fd) + " has an unknown Windows file type, error=" + std::to_string(::GetLastError()));
}
}
int AccessMode(int fd, const char* function) {
using QueryObject = NTSTATUS(NTAPI*)(HANDLE, OBJECT_INFORMATION_CLASS, PVOID, ULONG, PULONG);
static const auto query = reinterpret_cast<QueryObject>(reinterpret_cast<void*>(::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"), "NtQueryObject")));
PUBLIC_OBJECT_BASIC_INFORMATION information{};
const NTSTATUS status = query == nullptr ? -1 : query(NativeHandle(fd), ObjectBasicInformation, &information, sizeof(information), nullptr);
if (status < 0)
throw std::runtime_error(std::string(function) + ": cannot read the access of descriptor " + std::to_string(fd) + ", status=" + std::to_string(status));
const bool read = (information.GrantedAccess & FILE_READ_DATA) != 0;
const bool write = (information.GrantedAccess & (FILE_WRITE_DATA | FILE_APPEND_DATA)) != 0;
if (read && write) return ReadWrite;
if (write) return WriteOnly;
if (read) return ReadOnly;
throw std::runtime_error(std::string(function) + ": descriptor " + std::to_string(fd) + " grants neither read nor write access");
}
DWORD PipeState(int fd, const char* function) {
DWORD state = 0;
if (!::GetNamedPipeHandleStateW(NativeHandle(fd), &state, nullptr, nullptr, nullptr, nullptr, 0))
throw std::runtime_error(std::string(function) + ": cannot read the mode of pipe descriptor " + std::to_string(fd) + ", error=" + std::to_string(::GetLastError()));
return state;
}
void SetPipeNonblocking(int fd, bool nonblocking, const char* function) {
DWORD mode = (PipeState(fd, function) & PIPE_READMODE_MESSAGE) | (nonblocking ? PIPE_NOWAIT : PIPE_WAIT);
if (!::SetNamedPipeHandleState(NativeHandle(fd), &mode, nullptr, nullptr))
throw std::runtime_error(std::string(function) + ": cannot change the mode of pipe descriptor " + std::to_string(fd) + ", error=" + std::to_string(::GetLastError()));
}
Entry InitialEntry(int fd) {
int status = 0;
if (::GetFileType(NativeHandle(fd)) == FILE_TYPE_PIPE && (PipeState(fd, "fcntl") & PIPE_NOWAIT) != 0) status |= Nonblocking;
return {false, std::make_shared<int>(status)};
}
void ApplyHostStatus(int fd, Kind kind, int changed, int next, const char* function) {
if ((changed & Append) && kind == Kind::File) Unsupported(function, "changing O_APPEND", kind, fd);
if ((changed & Nonblocking) && kind == Kind::Device) Unsupported(function, "changing O_NONBLOCK", kind, fd);
if ((changed & Nonblocking) && kind == Kind::Pipe) SetPipeNonblocking(fd, (next & Nonblocking) != 0, function);
}
int Readable(int fd, Kind kind, const char* function) {
if (kind == Kind::File) {
const auto size = ::_filelengthi64(fd);
const auto offset = ::_telli64(fd);
if (size < 0 || offset < 0)
throw std::runtime_error(std::string(function) + ": cannot read the size and offset of descriptor " + std::to_string(fd) + ", errno=" + std::to_string(errno));
return static_cast<int>(size - offset);
}
if (AccessMode(fd, function) == WriteOnly) return 0;
DWORD available = 0;
if (!::PeekNamedPipe(NativeHandle(fd), nullptr, 0, nullptr, &available, nullptr)) {
const DWORD error = ::GetLastError();
if (error == ERROR_BROKEN_PIPE) return 0;
throw std::runtime_error(std::string(function) + ": cannot peek pipe descriptor " + std::to_string(fd) + ", error=" + std::to_string(error));
}
return static_cast<int>(available);
}
#else
std::optional<Kind> Classify(int fd, const char* function) {
if (fd < 0) return std::nullopt;
struct stat status{};
if (::fstat(fd, &status) != 0) {
if (errno == EBADF) return std::nullopt;
throw std::runtime_error(std::string(function) + ": fstat failed, fd=" + std::to_string(fd) + ", errno=" + std::to_string(errno));
}
if (File::IsRandomDevice(fd)) return Kind::RandomDevice;
if (S_ISREG(status.st_mode)) return Kind::File;
if (S_ISDIR(status.st_mode)) return Kind::Directory;
if (S_ISFIFO(status.st_mode)) return Kind::Pipe;
if (S_ISCHR(status.st_mode)) return Kind::Device;
throw std::runtime_error(std::string(function) + ": descriptor " + std::to_string(fd) + " is neither a file, a directory, a pipe nor a character device");
}
int HostStatus(int fd, const char* function) {
const int flags = ::fcntl(fd, F_GETFL);
if (flags < 0) throw std::runtime_error(std::string(function) + ": host F_GETFL failed, fd=" + std::to_string(fd) + ", errno=" + std::to_string(errno));
return flags;
}
int AccessMode(int fd, const char* function) {
return HostStatus(fd, function) & O_ACCMODE;
}
Entry InitialEntry(int fd) {
const int descriptorFlags = ::fcntl(fd, F_GETFD);
if (descriptorFlags < 0) throw std::runtime_error("fcntl: host F_GETFD failed, fd=" + std::to_string(fd) + ", errno=" + std::to_string(errno));
const int host = HostStatus(fd, "fcntl");
int status = 0;
if (host & O_NONBLOCK) status |= Nonblocking;
if (host & O_APPEND) status |= Append;
if (host & O_ASYNC) status |= Asynchronous;
if ((host & O_SYNC) == O_SYNC) status |= Synchronous;
if (host & O_DIRECT) status |= Direct;
return {(descriptorFlags & FD_CLOEXEC) != 0, std::make_shared<int>(status)};
}
void ApplyHostStatus(int fd, Kind, int changed, int next, const char* function) {
if ((changed & (Nonblocking | Append)) == 0) return;
const int host = HostStatus(fd, function);
const int wanted = (host & ~(O_NONBLOCK | O_APPEND)) | ((next & Nonblocking) ? O_NONBLOCK : 0) | ((next & Append) ? O_APPEND : 0);
if (::fcntl(fd, F_SETFL, wanted) != 0)
throw std::runtime_error(std::string(function) + ": host F_SETFL failed, fd=" + std::to_string(fd) + ", errno=" + std::to_string(errno));
}
int Readable(int fd, Kind kind, const char* function) {
if (kind == Kind::File) {
struct stat status{};
const auto offset = ::lseek(fd, 0, SEEK_CUR);
if (::fstat(fd, &status) != 0 || offset < 0)
throw std::runtime_error(std::string(function) + ": cannot read the size and offset of descriptor " + std::to_string(fd) + ", errno=" + std::to_string(errno));
return static_cast<int>(status.st_size - offset);
}
if (AccessMode(fd, function) == WriteOnly) return 0;
int available = 0;
if (::ioctl(fd, FIONREAD, &available) != 0)
throw std::runtime_error(std::string(function) + ": host FIONREAD failed, fd=" + std::to_string(fd) + ", errno=" + std::to_string(errno));
return available;
}
int LockError(int error) {
switch (error) {
case EACCES:
case EAGAIN: return GuestEagain;
case EDEADLK: return GuestEdeadlk;
case EINTR: return GuestEintr;
case ENOLCK: return GuestEnolck;
case EBADF: return GuestEbadf;
case EINVAL: return GuestEinval;
case EOVERFLOW: return GuestEoverflow;
default: throw std::runtime_error("fcntl: host record lock failed, errno=" + std::to_string(error));
}
}
#endif
Entry& Materialize(int fd) {
const auto found = g_entries.find(fd);
if (found != g_entries.end()) return found->second;
return g_entries.emplace(fd, InitialEntry(fd)).first->second;
}
int ChangeStatus(int fd, Kind kind, int mask, int value, const char* function) {
std::lock_guard lock(g_mutex);
auto& status = *Materialize(fd).status;
const int next = (status & ~mask) | (value & mask);
const int changed = status ^ next;
if ((changed & Asynchronous) && (kind == Kind::Pipe || kind == Kind::Device)) Unsupported(function, "O_ASYNC", kind, fd);
if ((changed & Synchronous) && kind == Kind::File) Unsupported(function, "changing O_FSYNC", kind, fd);
ApplyHostStatus(fd, kind, changed, next, function);
status = next;
return 0;
}
int CurrentStatus(int fd) {
std::lock_guard lock(g_mutex);
return *Materialize(fd).status;
}
void SetCloseOnExec(int fd, bool value) {
std::lock_guard lock(g_mutex);
Materialize(fd).closeOnExec = value;
}
int RecordLock(int fd, int command, std::intptr_t argument) {
const bool old = command <= OldSetLockWait;
const int current = old ? command + (GetLock - OldGetLock) : command;
const std::size_t size = old ? OldGuestLockSize : sizeof(GuestLock);
auto* pointer = reinterpret_cast<void*>(argument);
if (pointer == nullptr) return Fail(GuestEfault);
GuestLock lock{};
std::memcpy(&lock, pointer, size);
const auto kind = Classify(fd, "fcntl");
if (!kind || *kind == Kind::Pipe) return Fail(GuestEbadf);
if (*kind != Kind::File && *kind != Kind::Directory) Unsupported("fcntl", "a record lock", *kind, fd);
#ifdef _WIN32
static_cast<void>(current);
throw std::runtime_error("fcntl: record locks are not implemented on Windows, command=" + std::to_string(command) + ", fd=" + std::to_string(fd));
#else
struct flock host{};
if (lock.type == ReadLock) host.l_type = F_RDLCK;
else if (lock.type == WriteLock) host.l_type = F_WRLCK;
else if (lock.type == Unlock) host.l_type = F_UNLCK;
else return Fail(GuestEinval);
if (current == GetLock && lock.type == Unlock)
throw std::runtime_error("fcntl: F_GETLK with F_UNLCK is not implemented, fd=" + std::to_string(fd));
host.l_whence = lock.whence;
host.l_start = static_cast<off_t>(lock.start);
host.l_len = static_cast<off_t>(lock.length);
const int hostCommand = current == GetLock ? F_GETLK : current == SetLock ? F_SETLK : F_SETLKW;
if (::fcntl(fd, hostCommand, &host) != 0) return Fail(LockError(errno));
if (current != GetLock) return 0;
if (host.l_type == F_UNLCK) {
if (lock.whence == FromCurrent) {
const auto offset = ::lseek(fd, 0, SEEK_CUR);
if (offset < 0) throw std::runtime_error("fcntl: cannot read the offset of descriptor " + std::to_string(fd) + ", errno=" + std::to_string(errno));
lock.start += offset;
}
lock.type = Unlock;
} else {
lock.type = host.l_type == F_RDLCK ? ReadLock : WriteLock;
lock.whence = SEEK_SET;
lock.start = host.l_start;
lock.length = host.l_len;
lock.pid = host.l_pid;
lock.system = 0;
}
const GuestArena::HostWrite destination(pointer, size);
if (!destination.Open()) return Fail(GuestEfault);
std::memcpy(pointer, &lock, size);
return 0;
#endif
}
int ReadAheadControl(int fd, int command, std::intptr_t argument) {
const auto kind = Classify(fd, "fcntl");
if (!kind || *kind == Kind::Pipe) return Fail(GuestEbadf);
if (*kind != Kind::File) return Fail(GuestEnotty);
std::lock_guard lock(g_mutex);
auto& status = *Materialize(fd).status;
if (command == DarwinReadAhead || argument >= 0) status |= ReadAheadFlag;
else status &= ~ReadAheadFlag;
return 0;
}
}
namespace File {
FcntlArgument FcntlArgumentOf(int command) {
switch (command) {
case DuplicateCommand:
case SetDescriptorFlags:
case SetStatusFlags:
case SetOwner:
case DuplicateToCommand:
case ReadAhead:
case DarwinReadAhead:
case DuplicateCloseOnExec:
case DuplicateToCloseOnExec:
return FcntlArgument::Int;
case OldGetLock:
case OldSetLock:
case OldSetLockWait:
case GetLock:
case SetLock:
case SetLockWait:
case SetLockRemote:
return FcntlArgument::Pointer;
default:
return FcntlArgument::None;
}
}
int Fcntl(int fd, int command, std::intptr_t argument) {
switch (command) {
case DuplicateCommand:
case DuplicateCloseOnExec: {
if (fd < 0) return Fail(GuestEbadf);
if (static_cast<int>(argument) < 0) return Fail(GuestEinval);
if (!Classify(fd, "fcntl")) return Fail(GuestEbadf);
const int duplicate = DuplicateFrom(fd, static_cast<int>(argument));
if (duplicate >= 0 && command == DuplicateCloseOnExec) SetCloseOnExec(duplicate, true);
return duplicate;
}
case DuplicateToCommand:
case DuplicateToCloseOnExec: {
const int duplicate = dup2_nid_postfix(fd, static_cast<int>(argument));
if (duplicate >= 0 && command == DuplicateToCloseOnExec) SetCloseOnExec(duplicate, true);
return duplicate;
}
case GetDescriptorFlags: {
if (!Classify(fd, "fcntl")) return Fail(GuestEbadf);
std::lock_guard lock(g_mutex);
return Materialize(fd).closeOnExec ? CloseOnExec : 0;
}
case SetDescriptorFlags:
if (!Classify(fd, "fcntl")) return Fail(GuestEbadf);
SetCloseOnExec(fd, (argument & CloseOnExec) != 0);
return 0;
case GetStatusFlags: {
if (!Classify(fd, "fcntl")) return Fail(GuestEbadf);
const int access = AccessMode(fd, "fcntl");
return access | CurrentStatus(fd);
}
case SetStatusFlags: {
const auto kind = Classify(fd, "fcntl");
if (!kind) return Fail(GuestEbadf);
return ChangeStatus(fd, *kind, SettableStatusFlags, static_cast<int>(argument), "fcntl");
}
case GetOwner:
case SetOwner: {
const auto kind = Classify(fd, "fcntl");
if (!kind) return Fail(GuestEbadf);
Unsupported("fcntl", command == GetOwner ? "F_GETOWN" : "F_SETOWN", *kind, fd);
}
case OldGetLock:
case OldSetLock:
case OldSetLockWait:
case GetLock:
case SetLock:
case SetLockWait:
return RecordLock(fd, command, argument);
case SetLockRemote:
throw std::runtime_error("fcntl: F_SETLK_REMOTE needs a privilege whose state on the console is not known, fd=" + std::to_string(fd));
case ReadAhead:
case DarwinReadAhead:
return ReadAheadControl(fd, command, argument);
default:
return Fail(GuestEinval);
}
}
int Ioctl(int fd, std::uint64_t request, void* argument) {
const auto command = static_cast<std::uint32_t>(request);
const std::uint32_t length = (command >> 16) & 0x1fff;
if ((command & RequestIn) && length != 0 && argument == nullptr) return Fail(GuestEfault);
const auto kind = Classify(fd, "ioctl");
if (!kind) return Fail(GuestEbadf);
if (command == NonblockingRequest) {
const bool enable = *static_cast<const int*>(argument) != 0;
return ChangeStatus(fd, *kind, Nonblocking, enable ? Nonblocking : 0, "ioctl");
}
if (command == ReadableRequest) {
if (*kind == Kind::RandomDevice) return Fail(GuestEnotty);
if (*kind != Kind::File && *kind != Kind::Pipe) Unsupported("ioctl", "FIONREAD", *kind, fd);
const int available = Readable(fd, *kind, "ioctl");
if (argument == nullptr) return Fail(GuestEfault);
const GuestArena::HostWrite destination(argument, sizeof(available));
if (!destination.Open()) return Fail(GuestEfault);
std::memcpy(argument, &available, sizeof(available));
return 0;
}
if (((command >> 8) & 0xff) == TerminalGroup) return Fail(GuestEnotty);
char hex[11];
std::snprintf(hex, sizeof(hex), "0x%08X", command);
Unsupported("ioctl", std::string("request ") + hex, *kind, fd);
}
void RememberDescriptor(int fd, int openFlags) {
std::lock_guard lock(g_mutex);
g_entries[fd] = Entry{(openFlags & OpenCloseOnExec) != 0, std::make_shared<int>(openFlags & OpenStatusFlags)};
}
void ShareDescriptor(int from, int to) {
std::lock_guard lock(g_mutex);
auto status = Materialize(from).status;
g_entries[to] = Entry{false, std::move(status)};
}
void ForgetDescriptor(int fd) {
std::lock_guard lock(g_mutex);
g_entries.erase(fd);
}
}

View File

@ -6,6 +6,7 @@
#include "prx/libkernel/File/include/File.hpp"
#include "prx/libkernel/File/include/SharedMemory.hpp"
#include "prx/libkernel/File/include/DirectoryDescriptor.hpp"
#include "prx/libkernel/File/include/DescriptorControl.hpp"
#include "prx/libkernel/File/include/FileLock.hpp"
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
#include "prx/libkernel/File/include/RandomDevice.hpp"
@ -48,14 +49,19 @@ static int NativeRead(int fd, void* buf, std::size_t n) {
if (n > static_cast<std::size_t>(std::numeric_limits<unsigned int>::max())) {
throw std::runtime_error("sceKernelRead: nbytes exceeds platform limit");
}
return ::_read(fd, buf, static_cast<unsigned int>(n));
const int result = ::_read(fd, buf, static_cast<unsigned int>(n));
if (result < 0 && _doserrno == ERROR_NO_DATA) errno = EAGAIN;
return result;
}
static int NativeWrite(int fd, const void* buf, std::size_t n) {
if (n > static_cast<std::size_t>(std::numeric_limits<unsigned int>::max())) {
throw std::runtime_error("sceKernelWrite: nbytes exceeds platform limit");
}
const auto result = File::WriteSharedMemory_nid_no_patch(fd, buf, n);
return result == -2 ? ::_write(fd, buf, static_cast<unsigned int>(n)) : static_cast<int>(result);
const auto shared = File::WriteSharedMemory_nid_no_patch(fd, buf, n);
if (shared != -2) return static_cast<int>(shared);
const int result = ::_write(fd, buf, static_cast<unsigned int>(n));
if (result < 0 && errno == ENOSPC && _doserrno == 0 && ::GetFileType(reinterpret_cast<HANDLE>(::_get_osfhandle(fd))) == FILE_TYPE_PIPE) errno = EAGAIN;
return result;
}
extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handler(_invalid_parameter_handler);
static void IgnoreInvalidParameter(const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, std::uintptr_t) {}
@ -177,6 +183,7 @@ static int OpenNative(const std::filesystem::path& native, int flags, std::uint1
}
if ((flags & SCE_KERNEL_O_ACCMODE) != SCE_KERNEL_O_RDONLY || (flags & (SCE_KERNEL_O_CREAT | SCE_KERNEL_O_TRUNC)))
RecordWrittenPath_nid_no_patch(native);
File::RememberDescriptor(fd, flags);
return fd;
}
@ -188,7 +195,9 @@ int APS5_VABI sceKernelOpen(const char* path, int flags, std::uint16_t mode) {
if (path != nullptr && File::IsRandomDevicePath(path)) {
if ((flags & SCE_KERNEL_O_ACCMODE) != SCE_KERNEL_O_RDONLY) throw std::runtime_error(std::string(__func__) + ": writing to " + path + " is not implemented");
const int fd = File::OpenRandomDevice();
return fd < 0 ? SceErrorFromErrno(errno) : fd;
if (fd < 0) return SceErrorFromErrno(errno);
File::RememberDescriptor(fd, flags);
return fd;
}
std::filesystem::path native;
if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error);
@ -204,6 +213,7 @@ int OpenInDirectory_nid_no_patch(int directory, const char* path, int flags, std
int APS5_VABI sceKernelClose(int d) {
File::ForgetRandomDevice(d);
File::ForgetDescriptor(d);
#ifdef _WIN32
File::ForgetDirectoryDescriptor(d);
File::ForgetFileLock(d);
@ -300,27 +310,27 @@ int APS5_VABI sceKernelUnlink(const char* path) {
}
int APS5_VABI sceKernelFcntl(int d, int command, ...) {
if (d < GuestSockets::FirstDescriptor) {
if (!DescriptorIsOpen_nid_no_patch(d)) return SCE_KERNEL_ERROR_EBADF;
throw std::runtime_error(std::string(__func__) + ": file descriptors are not implemented, fd=" + std::to_string(d));
}
int result;
if (command == GuestSetFlags) {
const auto type = File::FcntlArgumentOf(command);
std::intptr_t argument = 0;
if (type != File::FcntlArgument::None) {
#ifdef _WIN32
__builtin_sysv_va_list arguments;
__builtin_sysv_va_start(arguments, command);
const int flags = __builtin_va_arg(arguments, int);
argument = type == File::FcntlArgument::Int ? __builtin_va_arg(arguments, int)
: reinterpret_cast<std::intptr_t>(__builtin_va_arg(arguments, void*));
__builtin_sysv_va_end(arguments);
#else
std::va_list arguments;
va_start(arguments, command);
const int flags = va_arg(arguments, int);
argument = type == File::FcntlArgument::Int ? va_arg(arguments, int)
: reinterpret_cast<std::intptr_t>(va_arg(arguments, void*));
va_end(arguments);
#endif
result = fcntl_nid_postfix(d, command, flags);
} else {
result = fcntl_nid_postfix(d, command);
}
int result;
if (d < GuestSockets::FirstDescriptor) result = File::Fcntl(d, command, argument);
else if (command == GuestSetFlags) result = fcntl_nid_postfix(d, command, static_cast<int>(argument));
else result = fcntl_nid_postfix(d, command);
return result < 0 ? SceKernelError(*__error_nid_postfix()) : result;
}

View File

@ -12,6 +12,7 @@
#include "prx/libkernel/File/include/NativeStat.hpp"
#include "prx/libkernel/File/include/KernelStatfs.hpp"
#include "prx/libkernel/File/include/DirectoryDescriptor.hpp"
#include "prx/libkernel/File/include/DescriptorControl.hpp"
#include "prx/libkernel/File/include/FileLock.hpp"
#include "prx/libkernel/File/include/RandomDevice.hpp"
#include "prx/libkernel/KernelErrors.hpp"
@ -419,6 +420,8 @@ extern "C" int APS5_VABI pipe_nid_postfix(int* descriptors) {
const int result = ::pipe(native);
#endif
if (result != 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff);
File::RememberDescriptor(native[0], 0);
File::RememberDescriptor(native[1], 0);
std::memcpy(descriptors, native, sizeof(native));
return 0;
}
@ -455,6 +458,28 @@ static bool HasNonDirectoryPrefix(const std::filesystem::path& native) {
return false;
}
extern "C" int APS5_VABI dup_nid_postfix(int d);
extern "C" int APS5_VABI close_nid_postfix(int d);
int File::DuplicateFrom(int fd, int start) {
#ifdef _WIN32
std::vector<int> below;
int duplicate;
while ((duplicate = dup_nid_postfix(fd)) >= 0 && duplicate < start) below.push_back(duplicate);
const int error = *__error_nid_postfix();
for (const int descriptor : below) close_nid_postfix(descriptor);
return duplicate < 0 ? PosixFailure(error) : duplicate;
#else
const int duplicate = ::fcntl(fd, F_DUPFD, start);
if (duplicate < 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff);
if (duplicate >= GuestSockets::FirstDescriptor)
throw std::runtime_error("fcntl: host descriptor reached the guest socket range");
if (File::IsRandomDevice(fd)) File::RememberRandomDevice(duplicate);
File::ShareDescriptor(fd, duplicate);
return duplicate;
#endif
}
extern "C" {
int APS5_VABI chmod_nid_postfix(const char* path, int mode) {
@ -474,6 +499,7 @@ int APS5_VABI close_nid_postfix(int d) {
return -1;
}
File::ForgetRandomDevice(d);
File::ForgetDescriptor(d);
#ifdef _WIN32
File::ForgetDirectoryDescriptor(d);
File::ForgetFileLock(d);
@ -500,6 +526,7 @@ int APS5_VABI dup_nid_postfix(int d) {
if (duplicate >= GuestSockets::FirstDescriptor)
throw std::runtime_error(std::string(__func__) + ": host descriptor reached the guest socket range");
if (File::IsRandomDevice(d)) File::RememberRandomDevice(duplicate);
File::ShareDescriptor(d, duplicate);
return duplicate;
}
@ -524,6 +551,7 @@ int APS5_VABI dup2_nid_postfix(int from, int to) {
File::ForgetFileLock(to);
File::ForgetRandomDevice(to);
if (sourceIsRandom) File::RememberRandomDevice(to);
File::ShareDescriptor(from, to);
return to;
#else
if (from == to) {
@ -534,6 +562,7 @@ int APS5_VABI dup2_nid_postfix(int from, int to) {
if (result < 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff);
File::ForgetRandomDevice(to);
if (sourceIsRandom) File::RememberRandomDevice(to);
File::ShareDescriptor(from, to);
return result;
#endif
}

View File

@ -17,6 +17,7 @@
#include "prx/libc/include/HostThreadLocal.hpp"
#include "prx/libkernel/Socket/include/SocketError.hpp"
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
#include "prx/libkernel/File/include/DescriptorControl.hpp"
#include "prx/libkernel/Equeue/Equeue.hpp"
#include "prx/libkernel/Time/include/TimedWait.hpp"
#include "prx/libkernel/Socket/include/SocketPoll.hpp"
@ -436,6 +437,26 @@ bool GuestSockets::Ready(int descriptor, bool write, std::int64_t* data, bool* e
extern "C" {
int APS5_VABI fcntl_nid_postfix(int descriptor, int command, ...) {
if (descriptor < GuestSockets::FirstDescriptor) {
const auto type = File::FcntlArgumentOf(command);
std::intptr_t argument = 0;
if (type != File::FcntlArgument::None) {
#ifdef _WIN32
__builtin_sysv_va_list arguments;
__builtin_sysv_va_start(arguments, command);
argument = type == File::FcntlArgument::Int ? __builtin_va_arg(arguments, int)
: reinterpret_cast<std::intptr_t>(__builtin_va_arg(arguments, void*));
__builtin_sysv_va_end(arguments);
#else
std::va_list arguments;
va_start(arguments, command);
argument = type == File::FcntlArgument::Int ? va_arg(arguments, int)
: reinterpret_cast<std::intptr_t>(va_arg(arguments, void*));
va_end(arguments);
#endif
}
return File::Fcntl(descriptor, command, argument);
}
const auto socket = Lookup(descriptor);
if (!socket) return -1;
std::lock_guard lock(socket->modeMutex);
@ -658,6 +679,7 @@ int APS5_VABI getpeername_nid_postfix(int descriptor, void* address, std::uint32
return 0;
}
int APS5_VABI ioctl_nid_postfix(int descriptor, std::uint64_t request, void* argument) {
if (descriptor < GuestSockets::FirstDescriptor) return File::Ioctl(descriptor, request, argument);
const auto socket = Lookup(descriptor);
if (!socket) return -1;
if (!argument) return Fail(14);

View File

@ -15,6 +15,7 @@ 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();
}
@ -178,10 +179,39 @@ static void RandomDeviceDuplicates() {
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();
}

View File

@ -9,19 +9,73 @@
extern "C" {
int APS5_VABI socket_nid_postfix(int, int, int);
int APS5_VABI close_nid_postfix(int);
int APS5_VABI pipe_nid_postfix(int*);
int APS5_VABI fcntl_nid_postfix(int, int, ...);
int APS5_VABI ioctl_nid_postfix(int, std::uint64_t, void*);
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 __error_nid_postfix();
int APS5_VABI sceKernelFcntl(int, int, ...);
int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t);
int APS5_VABI sceKernelClose(int);
std::int64_t APS5_VABI sceKernelLseek(int, std::int64_t, int);
}
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 GetLock = 11;
static constexpr int SetLock = 12;
static constexpr int ReadAhead = 15;
static constexpr int DarwinReadAhead = 16;
static constexpr int DuplicateCloseOnExec = 17;
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 GuestEbadf = 9;
static constexpr int GuestEfault = 14;
static constexpr int GuestEinval = 22;
static constexpr int GuestEnotty = 25;
static constexpr int GuestEagain = 35;
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 int SceEbadf = static_cast<int>(0x80020009u);
static constexpr int SceEinval = static_cast<int>(0x80020016u);
static constexpr int SceEnotsup = static_cast<int>(0x8002002du);
int main() {
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 SocketsKeepTheirPath() {
const int socket = socket_nid_postfix(2, 2, 0);
Require(socket >= 0);
Require(sceKernelFcntl(socket, GetFlags) == ReadWrite);
@ -33,21 +87,156 @@ int main() {
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);
Require(close_nid_postfix(socket) == 0);
Require(sceKernelFcntl(socket, GetFlags) == SceEbadf);
Require(sceKernelFcntl(-1, GetFlags) == SceEbadf);
Require(sceKernelFcntl(12345, GetFlags) == SceEbadf);
const int file = sceKernelOpen("kernel_fcntl_probe.bin", 0x202, 0644);
}
static void FileFlagsRoundTrip() {
const int file = sceKernelOpen("kernel_fcntl_probe.bin", CreateReadWrite, 0644);
Require(file >= 0);
bool rejected = false;
try {
sceKernelFcntl(file, GetFlags);
} catch (const std::runtime_error& error) {
rejected = std::string(error.what()).rfind("sceKernelFcntl: file descriptors are not implemented", 0) == 0;
}
Require(rejected);
Require(sceKernelClose(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(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));
const int closing = fcntl_nid_postfix(file, DuplicateCloseOnExec, duplicate);
Require(closing > duplicate);
Require(fcntl_nid_postfix(closing, GetDescriptorFlags) == 1);
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, 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);
#ifdef _WIN32
GuestLock lock{0, 0, 0, 3, 0, 0};
Require(ThrowsWith([file, &lock] { fcntl_nid_postfix(file, SetLock, &lock); }, "fcntl: record locks are not implemented on Windows"));
#else
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);
lock.type = 2;
Require(fcntl_nid_postfix(file, SetLock, &lock) == 0);
lock.type = 7;
RequireFailure(fcntl_nid_postfix(file, SetLock, &lock), GuestEinval);
#endif
for (const int descriptor : {closing, 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);
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));
#ifdef _WIN32
Require(ThrowsWith([appending] { fcntl_nid_postfix(appending, SetFlags, 0); }, "fcntl: changing O_APPEND on a file is not implemented"));
#else
Require(fcntl_nid_postfix(appending, SetFlags, 0) == 0);
Require(fcntl_nid_postfix(appending, GetFlags) == WriteOnly);
#endif
Require(ThrowsWith([appending] { fcntl_nid_postfix(appending, SetFlags, Append | 0x80); }, "fcntl: changing O_FSYNC on a file is not implemented"));
Require(sceKernelClose(appending) == 0);
std::remove("kernel_fcntl_probe.bin");
}
static void PipeFlagsRoundTrip() {
int pipe[2];
Require(pipe_nid_postfix(pipe) == 0);
Require(fcntl_nid_postfix(pipe[0], GetFlags) == ReadOnly);
Require(fcntl_nid_postfix(pipe[1], GetFlags) == WriteOnly);
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(fcntl_nid_postfix(pipe[0], SetFlags, fcntl_nid_postfix(pipe[0], GetFlags) | NonBlocking) == 0);
Require(fcntl_nid_postfix(pipe[0], GetFlags) == (ReadOnly | 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) == ReadOnly);
int on = 1;
Require(ioctl_nid_postfix(pipe[0], NonblockingRequest, &on) == 0);
Require(fcntl_nid_postfix(pipe[0], GetFlags) == (ReadOnly | 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);
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);
}
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();
PipeFlagsRoundTrip();
StandardInputFlags();
return 0;
}

View File

@ -685,8 +685,9 @@ Throughout the project, every function at every stage either **does exactly what
- [getsid](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) throws: process sessions are not modelled. PPSA74126 imports it and fails to load without it; the title made no call to it in a 60 s run
- [sigaction](../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) records the handler, flags and mask of every signal from 1 to 128 and reports them back, following FreeBSD's `sigaction` (`EINVAL` for other signals, and for a handler on `SIGKILL` or `SIGSTOP`); `signal` records its handler with `SA_RESTART`, as FreeBSD's libc does. Only handlers without `SA_SIGINFO` on the signals `signal` already delivers (`SIGINT`, `SIGILL`, `SIGABRT`, `SIGFPE`, `SIGSEGV`, `SIGTERM`) run, the same way `signal` handlers do; their mask and flags are not applied. `SA_SIGINFO` handlers are recorded but never called, since that needs FreeBSD's `siginfo_t` and `ucontext_t`, and neither are the handlers of other signals (`SIGBUS`, `SIGHUP`, ...), whether they are set by `sigaction` or by `signal`, which records them too (every number from 1 to 128, with `SA_RESTART` and an empty mask replacing what `sigaction` stored; `SIGKILL` and `SIGSTOP` accept only the default handler in both calls, as in FreeBSD's `kern_sigaction`). `raise` of an `SA_SIGINFO` handler on a signal `signal` delivers throws instead of terminating, and `raise` and `kill` of the other signals still fail with `EINVAL`, recorded handler or not
- [kill](../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) follows FreeBSD's `kern_kill` and `killpg1` for the only guest process: `EINVAL` for signals outside 0 to 128, signal 0 only checks that the target exists, and its own pid, 0 and minus its own pid (the guest is taken to lead its own process group) signal it through `raise`, so they share `raise`'s limits above. Any other pid, and -1, which skips the caller, report `ESRCH`, so `EPERM` is never reported
- [sceKernelFcntl](../../core/libs/prx/libkernel/File/src/Open.cpp) (libkernel) handles socket descriptors through the POSIX `fcntl` export (`F_GETFL`, `F_SETFL` with `O_NONBLOCK`) and returns its guest errno as `SCE_KERNEL_ERROR_*`, the form the other `sceKernel` file exports use; the command semantics follow FreeBSD's [fcntl](https://github.com/freebsd/freebsd-src/blob/stable/11/sys/kern/kern_descrip.c), and a closed descriptor returns `SCE_KERNEL_ERROR_EBADF`. File descriptors throw: their open flags are not tracked, and `F_DUPFD`, `F_GETFD` and `F_SETFD` are unsupported on all descriptors. The return values and error codes are unverified on a console
- [ioctl, _ioctl](../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) handle only `FIONBIO` and `FIONREAD` on a socket. Any other request on a socket fails with `ENOTTY`, and any request on a file or pipe descriptor fails with `EBADF`, where FreeBSD answers `FIONBIO` and `FIONREAD` there too and fails other requests, termios ones included, with `ENOTTY`. Stremio Plus (PPSA74126) imports `_ioctl` for its statically linked `tc*` functions, which send `TIOCGETA`, `TIOCSETA`, `TIOCSPGRP` and the other termios requests
- [sceKernelFcntl](../../core/libs/prx/libkernel/File/src/Open.cpp) (libkernel) runs the POSIX `fcntl` below and returns its guest errno as `SCE_KERNEL_ERROR_*`, the form the other `sceKernel` file exports use. The return values and error codes are unverified on a console
- [fcntl](../../core/libs/prx/libkernel/File/src/DescriptorControl.cpp) (libkernel) follows FreeBSD 11.4's [`kern_fcntl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L479-L799) and [`kern_dup`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_descrip.c#L812) on file, directory, pipe and character device descriptors; a socket keeps the libSceNet path (`F_GETFL`, `F_SETFL` with `O_NONBLOCK`, `EINVAL` for every other command, `F_GETFD` and `F_SETFD` included). The close-on-exec flag and the status flags live in a guest table, the status flags shared by `dup`, `dup2` and `F_DUPFD` as in an open file description; a descriptor the table has not seen, such as 0 to 2, starts from the host's flags, since the console's are not known. `F_GETFL` takes the access mode from the host (the granted access of the handle on Windows), so pipe ends report `O_RDONLY` and `O_WRONLY` where FreeBSD's bidirectional pipes report `O_RDWR`. `F_DUPFD` and `F_DUPFD_CLOEXEC` return the lowest free host descriptor at or above the start, so the bound is the host table (`EMFILE` on Windows, `EINVAL` past `RLIMIT_NOFILE` on Linux), not the console's unknown `RLIMIT_NOFILE` and `kern.maxfilesperproc`. `O_NONBLOCK` switches a host pipe (`PIPE_NOWAIT` on Windows, where an empty read and a full write then report `EAGAIN`) and is only recorded on files, directories and the random device, as their reads never block; on a Windows console or other character device it throws. Changing `O_APPEND` on a Windows file, changing `O_FSYNC` on a file, `O_ASYNC` on a pipe or character device (`SIGIO` is not delivered), `F_GETOWN`, `F_SETOWN` and `F_SETLK_REMOTE` throw; `O_DIRECT`, `F_RDAHEAD` and `F_READAHEAD` are recorded as the hints they are. Record locks (`F_GETLK`, `F_SETLK`, `F_SETLKW` and their forms without `l_sysid`) use the host's POSIX locks on Linux and throw on Windows, and `F_GETLK` with `F_UNLCK` throws. The return values and error codes are unverified on a console
- [ioctl, _ioctl](../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) handle only `FIONBIO` and `FIONREAD` on a socket, and any other request there fails with `ENOTTY`. On the other descriptors they follow FreeBSD's [`kern_ioctl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/sys_generic.c#L758), [`vn_ioctl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/vfs_vnops.c#L1488) and [`pipe_ioctl`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/sys_pipe.c#L1346): `FIONBIO` sets `O_NONBLOCK` as `fcntl` does, and `FIONREAD` reports the size past the offset of a file, the queued bytes of a pipe's read end, 0 on its write end and `ENOTTY` on the random device, while it throws on a directory or another character device. Every termios request (group `t`, `TIOCGWINSZ` included) fails with `ENOTTY`, since those descriptors are taken as not a terminal, as in `tcgetattr`, and any other request throws. Stremio Plus (PPSA74126) imports `_ioctl` for its statically linked `tc*` functions, which send `TIOCGETA`, `TIOCSETA`, `TIOCSPGRP` and the other termios requests
- [sendfile](../../core/libs/prx/libkernel/Socket/src/Udp.cpp) (libkernel) follows FreeBSD 11's `sendfile` for a connected stream socket: headers, the file from `offset` (to its end when `nbytes` is 0) and trailers, the file offset left unchanged, `EAGAIN` with `*sbytes` set after a partial send on a non-blocking socket. It reads the file through `sceKernelPread` in 64 KiB blocks and sends each with `send`. A socket as the file fails with `EINVAL`, as on FreeBSD, but any other descriptor `sceKernelPread` reads, such as `/dev/urandom`, is sent where FreeBSD requires a regular file or shared memory, and a file descriptor as the socket fails with `EBADF`, as for `send`, where FreeBSD answers `ENOTSOCK`. `flags` are ignored: reads complete before sending, so `SF_NODISKIO` never fails with `EBUSY`, and `SF_SYNC` returns once the data is queued on the socket rather than sent. PS5MC (PPSA99420) imports it
- [pthread_kill](../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) returns the error number like FreeBSD's `pthread_kill`: `EINVAL` for signals outside 0 to 128 and `ESRCH` for a null thread. Signal 0 reports any other thread handle as existing, without checking that the thread is still alive. A signal to the calling thread goes through `raise`, so it shares `raise`'s limits; a signal to another thread throws, since guest signals are not delivered to a chosen thread
- [select](../../core/libs/prx/libkernel/Socket/src/Socket.cpp) (libkernel) validates `tv_usec` but saturates a valid timeout beyond the remaining `steady_clock` range at `time_point::max()`, so the wait can continue effectively indefinitely. The console's behavior for a valid timeout outside the host clock's range is unknown.