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
26 changes: 26 additions & 0 deletions core/libs/prx/libc/src/RuntimeSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
#include <random>
#include <string>
#include <cinttypes>
#ifdef _WIN32
#include <windows.h>
#endif

#include "prx/libc/include/General.hpp"
#include "prx/libc/include/SonyCrt.hpp"
Expand All @@ -32,6 +35,20 @@ std::mutex g_exitMutex;
std::vector<ExitDestructor> g_exitDestructors;
bool g_exitRunnerRegistered = false;

#ifdef _WIN32
struct UcrtStream {
char* pointer;
char* base;
int count;
long flags;
long file;
int charBuffer;
int bufferSize;
char* temporaryName;
CRITICAL_SECTION lock;
};
#endif

}

extern "C" {
Expand Down Expand Up @@ -142,6 +159,15 @@ void APS5_VABI funlockfile_nid_postfix(FileStream* stream) {
_Unlockfilelock_nid_postfix(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;
#else
return ftrylockfile(GetNativeStream(stream)) == 0 ? 0 : -1;
#endif
}

std::uint64_t APS5_VABI _Stoul_nid_postfix(const char* str, char** endptr, int base) {
if (StopAtPrefixLetter_nid_no_patch(str, endptr, base)) return 0;
return std::strtoull(str, endptr, base);
Expand Down
54 changes: 54 additions & 0 deletions core/libs/tests/GuestStandardStreams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <chrono>
#include <atomic>
#include <thread>
#include <stdexcept>
#ifndef _WIN32
#include <unistd.h>
#endif
Expand Down Expand Up @@ -55,6 +56,7 @@ int APS5_VABI _Getmbcurmax_nid_postfix();
int APS5_VABI ___mb_cur_max_nid_postfix();
void APS5_VABI flockfile_nid_postfix(FileStream*);
void APS5_VABI funlockfile_nid_postfix(FileStream*);
int APS5_VABI ftrylockfile_nid_postfix(FileStream*);
}
static void Require(bool value) { if (!value) std::abort(); }
static int APS5_VABI WriteFormatted(FileStream* stream, const char* format, ...) {
Expand Down Expand Up @@ -134,6 +136,57 @@ static void CheckUnlockedStreams() {
Require(std::filesystem::remove(filename));
}

static void CheckTryLock() {
FileStream stream(std::tmpfile());
const auto tryFromOtherThread = [&] {
int result = 0;
std::thread other([&] {
result = ftrylockfile_nid_postfix(&stream);
if (result == 0) funlockfile_nid_postfix(&stream);
});
other.join();
return result;
};
Require(ftrylockfile_nid_postfix(&stream) == 0);
Require(ftrylockfile_nid_postfix(&stream) == 0);
Require(tryFromOtherThread() == -1);
funlockfile_nid_postfix(&stream);
Require(tryFromOtherThread() == -1);
std::atomic<bool> written = false;
std::thread writer([&] {
Require(fputc_nid_postfix('B', &stream) == 'B');
written = true;
});
std::this_thread::sleep_for(std::chrono::milliseconds(50));
Require(!written && fputc_nid_postfix('A', &stream) == 'A');
funlockfile_nid_postfix(&stream);
writer.join();
Require(written && tryFromOtherThread() == 0);
std::atomic<bool> held = false, release = false;
std::thread owner([&] {
flockfile_nid_postfix(&stream);
held = true;
while (!release) std::this_thread::yield();
funlockfile_nid_postfix(&stream);
});
while (!held) std::this_thread::yield();
const int contended = ftrylockfile_nid_postfix(&stream);
release = true;
owner.join();
Require(contended == -1 && ftrylockfile_nid_postfix(&stream) == 0);
funlockfile_nid_postfix(&stream);
Require(tryFromOtherThread() == 0);
Require(fseeko_nid_postfix(&stream, 0, SEEK_SET) == 0);
Require(fgetc_nid_postfix(&stream) == 'A' && fgetc_nid_postfix(&stream) == 'B');
stream.Close();
bool threw = false;
try { ftrylockfile_nid_postfix(&stream); } catch (const std::runtime_error&) { threw = true; }
Require(threw);
threw = false;
try { ftrylockfile_nid_postfix(nullptr); } catch (const std::invalid_argument&) { threw = true; }
Require(threw);
}

static void CheckUnlockedStreamLock(bool write) {
FileStream stream(std::tmpfile());
Require(std::fputc('A', stream.GetHandle()) == 'A');
Expand Down Expand Up @@ -461,5 +514,6 @@ int main() {
CheckUnlockedStreams();
CheckUnlockedStreamLock(false);
CheckUnlockedStreamLock(true);
CheckTryLock();
return CheckBinaryModes() ? 0 : 1;
}
2 changes: 2 additions & 0 deletions docs/dev/TechnicalDebt.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,8 @@ Throughout the project, every function at every stage either **does exactly what

- [getc_unlocked, putc_unlocked](../../core/libs/prx/libc/src/StandardStreams.cpp) (libc) - byte I/O uses the host unlocked operations; guest EOF/error flags and macro counters follow `FileStream`. Linux status queries use `feof_unlocked`/`ferror_unlocked` because [glibc `feof`](https://github.com/bminor/glibc/blob/d2097651cc57834dbfcaa102ddfacae0d86cfb66/libio/feof.c) and [`ferror`](https://github.com/bminor/glibc/blob/d2097651cc57834dbfcaa102ddfacae0d86cfb66/libio/ferror.c) can acquire the stream lock. Windows uses [the CRT no-lock byte operations](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/fgetc-nolock-fgetwc-nolock) and `feof`/`ferror`, verified under a held stream lock on UCRT. Console behavior and other host CRTs remain unverified.

- [ftrylockfile](../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc) - tries the host CRT stream lock that `flockfile`, `funlockfile` and each locking host stdio call on the stream take, so it returns -1 while another thread holds the stream through `flockfile` or is inside such a call, and 0 when it acquires the lock or already owns it (recursively, released by as many `funlockfile` calls), as FreeBSD 11 [`_ftrylockfile`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdio/_flock_stub.c#L89-L104) does. A libc export holds the lock only for each host call it makes, not across several host calls or while it updates the guest `FILE` prefix. Linux uses glibc `ftrylockfile`. UCRT has no try variant of `_lock_file`, so Windows calls `TryEnterCriticalSection` on the `CRITICAL_SECTION` that `_lock_file` enters, found through the private UCRT stream layout (`__crt_stdio_stream_data` in `ucrt/inc/corecrt_internal_stdio.h`, unchanged from Windows SDK 10.0.19041 to 10.0.26100); another CRT or a change of that layout breaks it. A null or closed stream throws where FreeBSD dereferences it

- [libSceOpusDec](../../core/libs/prx/libSceOpusDec/Export.cpp) uses FFmpeg for 48000 Hz mono/stereo signed-16 decoding. The six signatures, 640-byte opaque state allocation and byte-count return follow [public PS5 decoder research](https://github.com/blackbearreloaded/ps5-hardware-audio-decoding/blob/26be5a12f92b2cdc67f8a9e082fd69ae0e9a35a2/docs/DECODING.md#direct-opus-abi) and [ProsperoRadio](https://github.com/blackbearreloaded/ProsperoRadio/blob/2c4029c3a8f43e9e1dfef98374d94d99b14d745d/src/opus_decoder.cpp). Console PCM equivalence and error codes are unverified. Other sample rates, packet loss concealment, invalid inputs and termination with live decoders throw. Context identifiers and host decoder state are private registries; the firmware's opaque state layout and error recovery are unknown.
- [mkstemp and isatty](../../core/libs/prx/libScePosixForWebKit/Filesystem.cpp) (libScePosixForWebKit) follow FreeBSD's [temporary-file API](https://github.com/freebsd/freebsd-src/blob/stable/9/lib/libc/stdio/mktemp.c) and [terminal check](https://github.com/freebsd/freebsd-src/blob/stable/9/lib/libc/gen/isatty.c), without console verification. `mkstemp` replaces all trailing `X` characters, accepts a fixed name, and creates files through the guest path resolver with exclusive read/write access and mode 0600. Windows file permissions follow the existing kernel file implementation. `isatty` tests Windows console handles rather than all character devices; files, pipes and guest sockets return `ENOTTY`, closed descriptors return `EBADF`.
- [arc4random](../../core/libs/prx/libScePosixForWebKit/Export.cpp) (libScePosixForWebKit) returns four bytes from the same thread-local host `std::random_device` as `arc4random_buf`, uniform over the 32-bit range like FreeBSD 11.4's RC4 keystream in [arc4random.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/arc4random.c). The console's generator and its reseeding are unknown; `arc4random_uniform`, `arc4random_stir` and `arc4random_addrandom` are not in the module's export set and are absent
Expand Down
Loading