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:
commit
5f3b94cb19
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue