Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion core/libs/prx/libc/include/FileStream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class FileStream {
std::FILE* _handle;
bool _dynamic;
bool guestError = false;
bool randomDevice = false;

public:
explicit FileStream(std::FILE* handle, bool dynamic = false) : _handle(handle), _dynamic(dynamic) {
Expand Down Expand Up @@ -109,10 +110,20 @@ class FileStream {
errno = error;
}

void MarkRandomDevice() {
randomDevice = true;
_guest.descriptor = -1;
}

bool IsRandomDevice() const {
return randomDevice;
}

bool Reopen(const std::filesystem::path& filename, const char* mode) {
auto* previous = GetHandle();
_guest = {};
guestError = false;
randomDevice = false;
#ifdef _WIN32
std::wstring wideMode;
for (const char character : std::string_view(mode)) wideMode.push_back(static_cast<unsigned char>(character));
Expand Down Expand Up @@ -189,11 +200,17 @@ class FileStream {
};
static_assert(std::is_standard_layout_v<FileStream>);

inline std::FILE* GetNativeStream(FileStream* stream) {
inline std::FILE* GetStreamHandle(FileStream* stream) {
if (!stream) throw std::runtime_error("FileStream: null stream");
return stream->GetHandle();
}

inline std::FILE* GetNativeStream(FileStream* stream) {
auto* handle = GetStreamHandle(stream);
if (stream->IsRandomDevice()) throw std::runtime_error("FileStream: this function is not implemented on a /dev/random or /dev/urandom stream");
return handle;
}

extern "C" {
extern FileStream _Stdout_nid_postfix;
extern FileStream _Stderr_nid_postfix;
Expand Down
27 changes: 24 additions & 3 deletions core/libs/prx/libc/src/FileAndHeap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <limits>
#include <new>
#include <stdexcept>
#include <string_view>
#include <utility>
#include <cerrno>
#include <cstring>
Expand All @@ -29,6 +30,7 @@
#include "prx/libc/include/HostErrno.hpp"
#include "prx/libc/include/AppArchive.hpp"
#include "prx/libc/include/GuestArena.hpp"
#include "prx/libc/include/HostEntropy.hpp"

#ifdef __linux__
#include <stdio_ext.h>
Expand Down Expand Up @@ -310,6 +312,19 @@ FileStream* APS5_VABI fopen_nid_postfix(const char* filename, const char* mode)
errno = EINVAL;
return nullptr;
}
if (const std::string_view path(filename); path == "/dev/random" || path == "/dev/urandom") {
if (parsed->writes) throw std::runtime_error(std::string(__func__) + ": writing to " + filename + " is not implemented");
#ifdef _WIN32
std::unique_ptr<std::FILE, int (*)(std::FILE*)> device(std::fopen("NUL", "rb"), std::fclose);
#else
std::unique_ptr<std::FILE, int (*)(std::FILE*)> device(std::fopen("/dev/null", "rb"), std::fclose);
#endif
if (!device) return nullptr;
auto stream = std::make_unique<FileStream>(device.get(), true);
device.release();
stream->MarkRandomDevice();
return stream.release();
}
std::filesystem::path fpath;
if (const int error = ResolvePathError_nid_no_patch(filename, fpath)) { errno = error; return nullptr; }
std::unique_ptr<std::FILE, int (*)(std::FILE*)> handle(OpenNative(fpath, parsed->native), std::fclose);
Expand Down Expand Up @@ -340,7 +355,7 @@ int APS5_VABI fclose_nid_postfix(FileStream* stream) {
errno = 9;
return -1;
}
GetNativeStream(stream);
GetStreamHandle(stream);
std::unique_ptr<FileStream> owner(stream->IsDynamic() ? stream : nullptr);
return stream->Close();
}
Expand Down Expand Up @@ -372,14 +387,19 @@ FileStream* APS5_VABI _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(const

size_t APS5_VABI fread_nid_postfix(void* buffer, size_t size, size_t count, FileStream* stream) {
if (stream && !stream->IsOpen()) { errno = 9; return 0; }
auto* handle = GetNativeStream(stream);
auto* handle = GetStreamHandle(stream);
if (size == 0 || count == 0) return 0;
if (!buffer) throw std::runtime_error("fread: null buffer");
if (count > std::numeric_limits<std::size_t>::max() / size) throw std::overflow_error("fread: buffer size overflow");
const auto bytes = size * count;
if (bytes > std::numeric_limits<std::uintptr_t>::max() - reinterpret_cast<std::uintptr_t>(buffer)) throw std::overflow_error("fread: buffer address overflow");
const GuestArena::HostWrite destination(buffer, bytes);
if (!destination.Open()) throw std::runtime_error("fread: buffer is not writable");
if (stream->IsRandomDevice()) {
HostEntropy_nid_no_patch(buffer, bytes);
stream->SyncStatus();
return count;
}
const auto result = std::fread(buffer, size, count, handle);
stream->SyncStatus();
return result;
Expand Down Expand Up @@ -461,7 +481,8 @@ int APS5_VABI fputs_nid_postfix(const char* str, FileStream* stream) {

int APS5_VABI fflush_nid_postfix(FileStream* stream) {
if (stream && !stream->IsOpen()) { errno = 9; return -1; }
std::FILE* native = stream ? GetNativeStream(stream) : nullptr;
std::FILE* native = stream ? GetStreamHandle(stream) : nullptr;
if (stream && stream->IsRandomDevice()) return 0;
#ifdef __linux__
if (native && !__fwriting(native)) return 0;
#endif
Expand Down
12 changes: 6 additions & 6 deletions core/libs/prx/libc/src/RuntimeSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,18 +135,18 @@ unsigned int APS5_VABI _ZSt14_Random_devicev_nid_postfix() {
void APS5_VABI _Lockfilelock_nid_postfix(FileStream* stream) {
if (stream == nullptr) throw std::invalid_argument("_Lockfilelock: null stream");
#ifdef _WIN32
_lock_file(GetNativeStream(stream));
_lock_file(GetStreamHandle(stream));
#else
flockfile(GetNativeStream(stream));
flockfile(GetStreamHandle(stream));
#endif
}

void APS5_VABI _Unlockfilelock_nid_postfix(FileStream* stream) {
if (stream == nullptr) throw std::invalid_argument("_Unlockfilelock: null stream");
#ifdef _WIN32
_unlock_file(GetNativeStream(stream));
_unlock_file(GetStreamHandle(stream));
#else
funlockfile(GetNativeStream(stream));
funlockfile(GetStreamHandle(stream));
#endif
}

Expand All @@ -161,9 +161,9 @@ void APS5_VABI funlockfile_nid_postfix(FileStream* stream) {
int APS5_VABI ftrylockfile_nid_postfix(FileStream* stream) {
if (stream == nullptr) throw std::invalid_argument("ftrylockfile: null stream");
#ifdef _WIN32
return TryEnterCriticalSection(&reinterpret_cast<UcrtStream*>(GetNativeStream(stream))->lock) ? 0 : -1;
return TryEnterCriticalSection(&reinterpret_cast<UcrtStream*>(GetStreamHandle(stream))->lock) ? 0 : -1;
#else
return ftrylockfile(GetNativeStream(stream)) == 0 ? 0 : -1;
return ftrylockfile(GetStreamHandle(stream)) == 0 ? 0 : -1;
#endif
}

Expand Down
2 changes: 1 addition & 1 deletion core/libs/prx/libc/src/StandardStreams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ int APS5_VABI setvbuf_nid_postfix(FileStream* stream, char* buffer, int mode, st
buffer = nullptr;
size = BUFSIZ;
}
return std::setvbuf(GetNativeStream(stream), buffer, native, size);
return std::setvbuf(GetStreamHandle(stream), buffer, native, size);
}
void APS5_VABI setbuf_nid_postfix(FileStream* stream, char* buffer) {
setvbuf_nid_postfix(stream, buffer, buffer ? 0 : 2, buffer ? 1024 : 0);
Expand Down
42 changes: 42 additions & 0 deletions core/libs/tests/GuestFilesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ int APS5_VABI chdir_nid_postfix(const char*);
void* APS5_VABI opendir_nid_postfix(const char*);
FileStream* APS5_VABI fopen_nid_postfix(const char*, const char*);
FileStream* APS5_VABI freopen_nid_postfix(const char*, const char*, FileStream*);
std::size_t APS5_VABI fread_nid_postfix(void*, std::size_t, std::size_t, FileStream*);
int APS5_VABI fclose_nid_postfix(FileStream*);
int APS5_VABI fgetc_nid_postfix(FileStream*);
int APS5_VABI feof_nid_postfix(FileStream*);
int APS5_VABI ferror_nid_postfix(FileStream*);
int APS5_VABI setvbuf_nid_postfix(FileStream*, char*, int, std::size_t);
int APS5_VABI fflush_nid_postfix(FileStream*);
void APS5_VABI flockfile_nid_postfix(FileStream*);
void APS5_VABI funlockfile_nid_postfix(FileStream*);
int APS5_VABI ftrylockfile_nid_postfix(FileStream*);
int* APS5_VABI __error_nid_postfix();
int APS5_VABI pipe_nid_postfix(int*);
int APS5_VABI dup2_nid_postfix(int, int);
Expand Down Expand Up @@ -1088,6 +1098,38 @@ int main() {
Require(read_nid_postfix(random, second, 3) == 3);
Require(std::memcmp(first, second, sizeof(first)) != 0 && second[3] == 0);
Require(close_nid_postfix(random) == 0);
auto* stream = fopen_nid_postfix(device, "rb");
Require(stream != nullptr);
Require(setvbuf_nid_postfix(stream, nullptr, 2, 0) == 0);
std::memset(first, 0, sizeof(first));
std::memset(second, 0, sizeof(second));
Require(fread_nid_postfix(first, 1, sizeof(first), stream) == sizeof(first));
Require(fread_nid_postfix(second, 8, 8, stream) == 8);
Require(std::memcmp(first, second, sizeof(first)) != 0);
Require(feof_nid_postfix(stream) == 0 && ferror_nid_postfix(stream) == 0);
flockfile_nid_postfix(stream);
Require(fread_nid_postfix(first, 1, 4, stream) == 4);
funlockfile_nid_postfix(stream);
Require(ftrylockfile_nid_postfix(stream) == 0);
funlockfile_nid_postfix(stream);
Require(fflush_nid_postfix(stream) == 0);
bool unsupported = false;
try {
fgetc_nid_postfix(stream);
} catch (const std::runtime_error&) {
unsupported = true;
}
Require(unsupported);
Require(fclose_nid_postfix(stream) == 0);
for (const char* mode : {"wb", "ab", "r+b"}) {
bool rejected = false;
try {
fopen_nid_postfix(device, mode);
} catch (const std::runtime_error&) {
rejected = true;
}
Require(rejected);
}
}
Require(unlink_nid_postfix(presentName.c_str()) == 0 && !std::filesystem::exists(present));
const auto empty = root / "empty";
Expand Down
1 change: 1 addition & 0 deletions docs/dev/debt/libc.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
- [sceKernelSync](../../../core/libs/prx/libc/src/General.cpp) (libkernel) flushes only the host files and directories recorded through `RecordWrittenPath_nid_no_patch` (the libc, libkernel, save data and app content file functions) and their changed parent directories. A host write in a library that does not record its path is not flushed by `sceKernelSync`
- [sceKernelSync](../../../core/libs/prx/libc/src/General.cpp) (libkernel) on Windows skips a recorded file it cannot open for writing (read-only, or held open by a handle that denies write sharing), since `FlushFileBuffers` needs a writable handle. On Linux, it skips a recorded file it cannot open for reading. Such a file's data may not reach the disk before a host crash; the guest reads the same data either way
- [`fopen` streams](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) on Windows share delete access, like the descriptors [`sceKernelOpen`](../../../core/libs/prx/libkernel/File/src/Open.cpp) opens, so a file the guest holds open can be removed or renamed. A stream reopened with `freopen` still goes through `_wfreopen`, which does not share delete access. On Windows, opening a directory in a read mode fails with `EACCES`, where FreeBSD opens it; directory streams are not emulated
- [`fopen` of /dev/random and /dev/urandom](../../../core/libs/prx/libc/src/FileAndHeap.cpp) (libc) in a read mode returns a stream over the host null device whose `fread` fills the buffer with host OS entropy, as the libkernel [random device](../../../core/libs/prx/libkernel/File/src/RandomDevice.cpp) descriptors do. Only the exact paths are recognised, the stream has no descriptor, `setvbuf`, `feof`, `ferror`, `clearerr`, `flockfile`, `ftrylockfile`, `funlockfile` and `fclose` work on it, `fflush` returns 0 as on a FreeBSD read-only stream, and every other stream function throws, as does opening the paths for writing. `freopen` onto these paths still fails with `ENOENT`. ProsperoTV (PPSA99003) reads its phone pairing code this way
- The [`_Unwind_Context`](../../../core/libs/prx/libc/include/exceptions/Unwind.hpp) that `_Unwind_Backtrace` passes to its callback starts with a table laid out like LLVM libunwind's `AbstractUnwindCursor` virtual table, because homebrew built with the ps5-payload-dev SDK links libunwind statically and reads the frames with its own `_Unwind_GetIP` (`__unw_get_reg`), which calls through that table. Only register reads and writes (`validReg`, `getReg`, `setReg`, with `UNW_REG_IP` and `UNW_REG_SP`), `validFloatReg` (always false) and `isSignalFrame` are answered; the other slots throw. The layout follows libunwind's `UnwindCursor.hpp`, not the console's own `libSceLibcInternal`, which is not checked
- The Windows libc [exception unwinder](../../../core/libs/prx/libc/src/exception/Unwind.cpp) steps through frames of modules without the project's `.ehmeta` and `.ehfram` tables (system DLLs, and WinLibs GCC 15.2's `libstdc++-6.dll` and `libwinpthread-1.dll`, whose `.eh_frame` sections are empty) with their PE unwind data through `RtlVirtualUnwind`, but does not run Windows exception or termination handlers: it stops at a frame whose unwind data has one, such as a C++ cleanup through `__gxx_personality_seh0` in `libstdc++-6.dll` or a system DLL function like `qsort` in `ucrtbase.dll` (Windows 10 22H2), and the exception is then reported as unhandled
- The libc [SSE4a trap emulation](../../../core/libs/prx/libc/src/specifics/windows/Sse4aEmulation.hpp) on Windows is superseded by `--to-intel` and remains only until the relinked title has been verified without it.
Expand Down
Loading