fix(libc): keep unlocked stream status queries lock-free
Use unlocked POSIX EOF/error queries after unlocked byte operations so status synchronization cannot reacquire the stream lock. Keep the verified UCRT status calls on Windows, and cover byte, status, error, and held-lock behavior. Co-authored-by: MarcZX100 <marczxrr@gmail.com>
This commit is contained in:
parent
753b856526
commit
228532e9a8
|
|
@ -97,6 +97,20 @@ public:
|
|||
(std::feof(GetHandle()) ? 0x20 : 0) | (std::ferror(GetHandle()) || encodingError ? 0x40 : 0));
|
||||
}
|
||||
|
||||
void SyncUnlockedStatus() {
|
||||
_guest.readRemaining = 0;
|
||||
_guest.writeRemaining = 0;
|
||||
#ifdef _WIN32
|
||||
const bool endOfFile = std::feof(GetHandle()) != 0;
|
||||
const bool error = std::ferror(GetHandle()) != 0;
|
||||
#else
|
||||
const bool endOfFile = ::feof_unlocked(GetHandle()) != 0;
|
||||
const bool error = ::ferror_unlocked(GetHandle()) != 0;
|
||||
#endif
|
||||
_guest.flags = static_cast<std::int16_t>((_guest.flags & ~0x60) |
|
||||
(endOfFile ? 0x20 : 0) | (error || encodingError ? 0x40 : 0));
|
||||
}
|
||||
|
||||
void SetEncodingError() {
|
||||
encodingError = true;
|
||||
SyncStatus();
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ int APS5_VABI getc_unlocked_nid_postfix(FileStream* stream) {
|
|||
#else
|
||||
const int result = ::getc_unlocked(GetNativeStream(stream));
|
||||
#endif
|
||||
stream->SyncStatus();
|
||||
stream->SyncUnlockedStatus();
|
||||
return result;
|
||||
}
|
||||
int APS5_VABI __srget_nid_postfix(FileStream* stream) { return fgetc_nid_postfix(stream); }
|
||||
|
|
@ -58,7 +58,7 @@ int APS5_VABI putc_unlocked_nid_postfix(int value, FileStream* stream) {
|
|||
#else
|
||||
const int result = ::putc_unlocked(value, GetNativeStream(stream));
|
||||
#endif
|
||||
stream->SyncStatus();
|
||||
stream->SyncUnlockedStatus();
|
||||
return result;
|
||||
}
|
||||
int APS5_VABI __swbuf_nid_postfix(int value, FileStream* stream) { return fputc_nid_postfix(value, stream); }
|
||||
|
|
|
|||
|
|
@ -97,6 +97,85 @@ static bool CheckFileBytes(const std::string& filename, const std::string& expec
|
|||
return false;
|
||||
}
|
||||
|
||||
static void CheckUnlockedStreams() {
|
||||
FileStream stream(std::tmpfile());
|
||||
auto& guest = stream.GuestState();
|
||||
const auto originalFlags = guest.flags;
|
||||
for (const int value : {0, 0x7f, 0x80, 0xff, 0x1ab, -2}) {
|
||||
guest.readRemaining = 13;
|
||||
guest.writeRemaining = 19;
|
||||
Require(putc_unlocked_nid_postfix(value, &stream) == static_cast<unsigned char>(value));
|
||||
Require(guest.readRemaining == 0 && guest.writeRemaining == 0 && guest.flags == originalFlags);
|
||||
}
|
||||
std::rewind(stream.GetHandle());
|
||||
for (const int value : {0, 0x7f, 0x80, 0xff, 0x1ab, -2}) {
|
||||
guest.readRemaining = 13;
|
||||
guest.writeRemaining = 19;
|
||||
Require(getc_unlocked_nid_postfix(&stream) == static_cast<unsigned char>(value));
|
||||
Require(guest.readRemaining == 0 && guest.writeRemaining == 0 && guest.flags == originalFlags);
|
||||
}
|
||||
Require(getc_unlocked_nid_postfix(&stream) == EOF && (guest.flags & 0x20));
|
||||
std::clearerr(stream.GetHandle());
|
||||
Require(std::ungetc('Q', stream.GetHandle()) == 'Q');
|
||||
Require(getc_unlocked_nid_postfix(&stream) == 'Q' && !(guest.flags & 0x20));
|
||||
std::rewind(stream.GetHandle());
|
||||
stream.SetEncodingError();
|
||||
Require(putc_unlocked_nid_postfix('R', &stream) == 'R' && (guest.flags & 0x40));
|
||||
clearerr_nid_postfix(&stream);
|
||||
Require(!(guest.flags & 0x40));
|
||||
stream.Close();
|
||||
|
||||
const auto filename = "anyps5-unlocked-test-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
|
||||
{ std::ofstream seed(filename, std::ios::binary); seed.put('A'); Require(seed.good()); }
|
||||
FileStream readOnly(std::fopen(filename.c_str(), "rb"));
|
||||
Require(putc_unlocked_nid_postfix('X', &readOnly) == EOF && (readOnly.GuestState().flags & 0x40));
|
||||
readOnly.Close();
|
||||
Require(CheckFileBytes(filename, "A", "rb", false));
|
||||
Require(std::filesystem::remove(filename));
|
||||
}
|
||||
|
||||
static void CheckUnlockedStreamLock(bool write) {
|
||||
FileStream stream(std::tmpfile());
|
||||
Require(std::fputc('A', stream.GetHandle()) == 'A');
|
||||
std::rewind(stream.GetHandle());
|
||||
std::atomic<bool> held = false, release = false, entered = false, done = false;
|
||||
int result = EOF;
|
||||
std::thread owner([&] {
|
||||
flockfile_nid_postfix(&stream);
|
||||
held = true;
|
||||
while (!release) std::this_thread::yield();
|
||||
funlockfile_nid_postfix(&stream);
|
||||
});
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(1);
|
||||
while (!held && std::chrono::steady_clock::now() < deadline) std::this_thread::yield();
|
||||
if (!held) {
|
||||
release = true;
|
||||
owner.join();
|
||||
Require(false);
|
||||
}
|
||||
std::thread caller([&] {
|
||||
entered = true;
|
||||
result = write ? putc_unlocked_nid_postfix('B', &stream) : getc_unlocked_nid_postfix(&stream);
|
||||
done = true;
|
||||
});
|
||||
const auto enteredDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(1);
|
||||
while (!entered && std::chrono::steady_clock::now() < enteredDeadline) std::this_thread::yield();
|
||||
if (!entered) {
|
||||
release = true;
|
||||
owner.join();
|
||||
caller.join();
|
||||
Require(false);
|
||||
}
|
||||
const auto completionDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(1);
|
||||
while (!done && std::chrono::steady_clock::now() < completionDeadline) std::this_thread::yield();
|
||||
const bool completedWhileHeld = done;
|
||||
release = true;
|
||||
owner.join();
|
||||
caller.join();
|
||||
Require(completedWhileHeld && result == (write ? 'B' : 'A'));
|
||||
stream.Close();
|
||||
}
|
||||
|
||||
static void CheckRewind() {
|
||||
FileStream stream(std::tmpfile());
|
||||
Require(fwrite_nid_postfix("ab", 1, 2, &stream) == 2);
|
||||
|
|
@ -369,5 +448,8 @@ int main() {
|
|||
Require(fgetc_nid_postfix(&locked) == 'A' && fgetc_nid_postfix(&locked) == 'B');
|
||||
locked.Close();
|
||||
CheckRewind();
|
||||
CheckUnlockedStreams();
|
||||
CheckUnlockedStreamLock(false);
|
||||
CheckUnlockedStreamLock(true);
|
||||
return CheckBinaryModes() ? 0 : 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -337,6 +337,8 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
|
||||
### Functional
|
||||
|
||||
- [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.
|
||||
|
||||
- [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`.
|
||||
- [Dinkumware thread exports](../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) follow the Dinkumware threads library shipped with [Visual C++ 2013](https://github.com/adityaramesh/concurrency_runtime/tree/master/VS2013_SP2/crt/src/thr) (`cthread.c`, `xtime.c`) and its `atomic.c`, [microsoft/STL `cthread.cpp`](https://github.com/microsoft/STL/blob/main/stl/src/cthread.cpp) and C11 Annex K, without console verification. `_Thrd_t` is taken to be the guest `pthread_t`, as the `std::_Pad::_Launch(pthread**)` export suggests: `_Thrd_join` returns `_Thrd_error` (4) when `pthread_join` fails and stores the thread's return value truncated to `int`. `_Thrd_sleep` reads a 16-byte xtime (64-bit seconds and nanoseconds) on the guest `CLOCK_REALTIME`, normalizes the nanoseconds and sleeps through `nanosleep`, so it is a cancellation point; a null xtime or one whose normalization overflows throws. The console's `_Thrd_sleep` (disassembled from a title's `libc.prx`) also takes an optional `xtime*` of remaining time and returns `int`; here the sleep is never cut short, so it returns 0 and leaves the remaining time untouched. `_Thrd_join` (32-bit store of the result, 0 or 4) and the spin lock (byte exchange loop, no yield) match that disassembly. The shared_ptr spin lock is one process-wide flag that yields while it spins. `wcstombs_s` is `wcsrtombs_s` with a fresh conversion state, so `*retval` excludes the terminating null as in Annex K, not as in the Microsoft CRT, and `_TRUNCATE` has no meaning
|
||||
|
|
|
|||
Loading…
Reference in New Issue