Files
AnyPS5/core/libs/tests/GuestStandardStreams.cpp
T
DavidandClaude Fable 5.1 9c7aa0d61a feat(libc): implement ftrylockfile
Porpoise PPSA99764 stops after pthread_attr_* (#3494) with the
predicted `FAIL: unresolved ELF import h05OHOMZNMw from
libSceLibcInternal.prx` (idx 311, call site 0x8EC37); RetroArch
PPSA99169 imports it at idx 448 (call site 0xA4A27). h05OHOMZNMw is
ftrylockfile by tools/nid_names.py and aerolib.csv (zecoxao/sce_symbols
2883963a); ps5-payload-dev/sdk f5d40f02 declares
`int ftrylockfile(FILE *)` in include/freebsd/stdio.h.

ftrylockfile tries the same host CRT stream lock that flockfile,
funlockfile and every locking host stdio call on the stream take, so
no second lock exists beside the one stdio uses. It returns 0 when the
lock is acquired or already owned by the caller (recursive, released by
as many funlockfile calls) and -1 when another thread holds it, as
FreeBSD 8c639945 lib/libc/stdio/_flock_stub.c:89-104 does. Linux calls
glibc ftrylockfile. UCRT has no try variant of _lock_file, so Windows
calls TryEnterCriticalSection on the CRITICAL_SECTION that _lock_file
enters (__crt_stdio_stream_data::_lock, ucrt/inc/corecrt_internal_stdio.h,
same layout in Windows SDK 10.0.19041, 10.0.22621 and 10.0.26100). A
null or closed stream throws. The UCRT layout dependency is recorded in
TechnicalDebt.

guest_standard_streams covers trylock on an unlocked stream, re-entry
by the owner, failure from another thread while held (also after one of
two unlocks and while another thread holds flockfile), stdio calls
from another thread blocking on the trylock-held lock, release order,
and null and closed streams.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 18:42:23 +05:30

520 lines
24 KiB
C++

#include "prx/libc/include/FileStream.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdlib>
#include <cstring>
#include <cstdarg>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <initializer_list>
#include <chrono>
#include <atomic>
#include <thread>
#include <stdexcept>
#ifndef _WIN32
#include <unistd.h>
#endif
extern "C" {
FileStream* APS5_VABI fopen_nid_postfix(const char*, const char*);
std::size_t APS5_VABI fread_nid_postfix(void*, std::size_t, std::size_t, FileStream*);
std::size_t APS5_VABI fwrite_nid_postfix(const void*, std::size_t, std::size_t, FileStream*);
FileStream* APS5_VABI freopen_nid_postfix(const char*, const char*, FileStream*);
int APS5_VABI fseeko_nid_postfix(FileStream*, std::int64_t, int);
std::int64_t APS5_VABI ftello_nid_postfix(FileStream*);
int APS5_VABI fseek_nid_postfix(FileStream*, std::int64_t, int);
std::int64_t APS5_VABI ftell_nid_postfix(FileStream*);
int* APS5_VABI __error_nid_postfix();
extern FileStream* __stdinp_nid_postfix;
extern FileStream* __stdoutp_nid_postfix;
extern FileStream* __stderrp_nid_postfix;
extern int __isthreaded_nid_postfix;
int APS5_VABI fprintf_nid_postfix(FileStream*, const char*, ...);
int APS5_VABI vfprintf_nid_postfix(FileStream*, const char*, void*);
int APS5_VABI vsprintf_nid_postfix(char*, const char*, void*);
int APS5_VABI fgetc_nid_postfix(FileStream*);
int APS5_VABI fputc_nid_postfix(int, FileStream*);
int APS5_VABI getc_unlocked_nid_postfix(FileStream*);
int APS5_VABI putc_unlocked_nid_postfix(int, FileStream*);
int APS5_VABI fputwc_nid_postfix(char16_t, FileStream*);
int APS5_VABI fputws_nid_postfix(const char16_t*, FileStream*);
int APS5_VABI fscanf_nid_postfix(FileStream*, const char*, ...);
int APS5_VABI __srget_nid_postfix(FileStream*);
int APS5_VABI __swbuf_nid_postfix(int, FileStream*);
int APS5_VABI ungetc_nid_postfix(int, FileStream*);
char* APS5_VABI fgets_nid_postfix(char*, int, FileStream*);
int APS5_VABI feof_nid_postfix(FileStream*);
int APS5_VABI ferror_nid_postfix(FileStream*);
int APS5_VABI fileno_nid_postfix(FileStream*);
void APS5_VABI clearerr_nid_postfix(FileStream*);
void APS5_VABI rewind_nid_postfix(FileStream*);
int APS5_VABI setvbuf_nid_postfix(FileStream*, char*, int, std::size_t);
void APS5_VABI setbuf_nid_postfix(FileStream*, char*);
FileStream* APS5_VABI fdopen_nid_postfix(int, const char*);
int APS5_VABI fclose_nid_postfix(FileStream*);
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, ...) {
#ifdef _WIN32
__builtin_sysv_va_list args;
__builtin_sysv_va_start(args, format);
#else
std::va_list args;
va_start(args, format);
#endif
const int result = vfprintf_nid_postfix(stream, format, args);
#ifdef _WIN32
__builtin_sysv_va_end(args);
#else
va_end(args);
#endif
return result;
}
static int APS5_VABI FormatString(char* buffer, const char* format, ...) {
#ifdef _WIN32
__builtin_sysv_va_list args;
__builtin_sysv_va_start(args, format);
#else
std::va_list args;
va_start(args, format);
#endif
const int result = vsprintf_nid_postfix(buffer, format, args);
#ifdef _WIN32
__builtin_sysv_va_end(args);
#else
va_end(args);
#endif
return result;
}
static bool CheckFileBytes(const std::string& filename, const std::string& expected, const char* mode, bool reopen) {
std::ifstream file(filename, std::ios::binary);
const std::string actual((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
if (actual == expected) return true;
std::fprintf(stderr, "%s %s: expected %zu raw bytes, received %zu\n", reopen ? "freopen" : "fopen", mode, expected.size(), actual.size());
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 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');
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);
*__error_nid_postfix() = 7;
rewind_nid_postfix(&stream);
Require(*__error_nid_postfix() == 7 && ftello_nid_postfix(&stream) == 0);
Require(fgetc_nid_postfix(&stream) == 'a' && ungetc_nid_postfix('z', &stream) == 'z');
rewind_nid_postfix(&stream);
Require(fgetc_nid_postfix(&stream) == 'a' && fgetc_nid_postfix(&stream) == 'b' && fgetc_nid_postfix(&stream) == EOF);
Require(feof_nid_postfix(&stream) != 0);
stream.SetEncodingError();
Require(ferror_nid_postfix(&stream) != 0);
rewind_nid_postfix(&stream);
Require(feof_nid_postfix(&stream) == 0 && ferror_nid_postfix(&stream) == 0 && fgetc_nid_postfix(&stream) == 'a');
stream.Close();
#ifndef _WIN32
int pipeEnds[2];
Require(::pipe(pipeEnds) == 0);
Require(::write(pipeEnds[1], "p", 1) == 1);
::close(pipeEnds[1]);
FileStream pipe(::fdopen(pipeEnds[0], "r"));
Require(fgetc_nid_postfix(&pipe) == 'p' && fgetc_nid_postfix(&pipe) == EOF && feof_nid_postfix(&pipe) != 0);
*__error_nid_postfix() = 0;
rewind_nid_postfix(&pipe);
Require(*__error_nid_postfix() == 29 && feof_nid_postfix(&pipe) == 0 && ferror_nid_postfix(&pipe) == 0);
pipe.Close();
#endif
}
static bool CheckBinaryModes() {
const auto directory = "anyps5-byte-stream-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
Require(std::filesystem::create_directory(directory));
const auto filename = directory + "/bytes";
const std::string original("A\r\n\x1a" "B\0C", 7);
const std::string written("D\n\x1a" "E\0F", 6);
const char* modes[] = {"r", "r+", "w", "w+", "a", "a+", "rb", "rb+", "r+b", "wb", "wb+", "w+b", "ab", "ab+", "a+b", "rt", "r+t", "wt", "w+e", "ae", "rbv"};
bool correct = true;
for (const auto* mode : modes) {
for (const bool reopen : {false, true}) {
{ std::ofstream seed(filename, std::ios::binary | std::ios::trunc); seed.write(original.data(), original.size()); Require(seed.good()); }
FileStream redirected(std::tmpfile());
FileStream* stream = reopen ? freopen_nid_postfix(filename.c_str(), mode, &redirected) : fopen_nid_postfix(filename.c_str(), mode);
Require(stream != nullptr);
const bool update = std::strchr(mode, '+') != nullptr;
if (*mode == 'r' || (*mode == 'a' && update)) {
Require(fseeko_nid_postfix(stream, 0, SEEK_SET) == 0);
char bytes[16]{};
const auto count = fread_nid_postfix(bytes, 1, sizeof(bytes), stream);
const bool matches = count == original.size() && std::memcmp(bytes, original.data(), original.size()) == 0;
if (!matches) std::fprintf(stderr, "%s %s: expected 7 input bytes, received %zu\n", reopen ? "freopen" : "fopen", mode, count);
correct &= matches;
Require(fseeko_nid_postfix(stream, 3, SEEK_SET) == 0);
correct &= fgetc_nid_postfix(stream) == 0x1a;
}
std::string expected = original;
if (*mode == 'w' || *mode == 'a' || update) {
Require(fseeko_nid_postfix(stream, 0, SEEK_SET) == 0);
Require(fwrite_nid_postfix(written.data(), 1, written.size(), stream) == written.size());
expected = *mode == 'w' ? written : *mode == 'a' ? original + written : written + original.substr(written.size());
}
Require(fclose_nid_postfix(stream) == 0);
correct &= CheckFileBytes(filename, expected, mode, reopen);
}
}
for (const auto* mode : {"", "q", "tr", "rx", "rbx"}) {
FileStream redirected(std::tmpfile());
Require(fopen_nid_postfix(filename.c_str(), mode) == nullptr && *__error_nid_postfix() == 22);
Require(freopen_nid_postfix(filename.c_str(), mode, &redirected) == nullptr && *__error_nid_postfix() == 22);
}
Require(fopen_nid_postfix(filename.c_str(), "wx") == nullptr && *__error_nid_postfix() == 17);
FileStream* readOnly = fopen_nid_postfix(filename.c_str(), "rt+");
Require(readOnly != nullptr && fputc_nid_postfix('X', readOnly) == EOF);
Require(fclose_nid_postfix(readOnly) == 0);
Require(std::filesystem::remove(filename));
FileStream* created = fopen_nid_postfix(filename.c_str(), "wx");
Require(created != nullptr && fputc_nid_postfix('Y', created) == 'Y');
Require(fclose_nid_postfix(created) == 0);
correct &= CheckFileBytes(filename, "Y", "wx", false);
Require(std::filesystem::remove(filename));
Require(std::filesystem::remove(directory));
return correct;
}
int main() {
Require(_Getmbcurmax_nid_postfix() == 1 && _Getmbcurmax_nid_postfix() == ___mb_cur_max_nid_postfix());
Require(fdopen_nid_postfix(-1, "rb") == nullptr && *__error_nid_postfix() == 9);
Require(fdopen_nid_postfix(0, nullptr) == nullptr && *__error_nid_postfix() == 22);
Require(fdopen_nid_postfix(0, "invalid") == nullptr && *__error_nid_postfix() == 22);
std::FILE* original = std::tmpfile();
Require(original != nullptr);
std::fputs("retained", original);
std::fflush(original);
#ifdef _WIN32
const int descriptor = _dup(_fileno(original));
#else
const int descriptor = ::dup(::fileno(original));
#endif
Require(descriptor >= 0);
auto* wrapped = fdopen_nid_postfix(descriptor, "r+b");
Require(wrapped != nullptr && fileno_nid_postfix(wrapped) == descriptor);
setbuf_nid_postfix(wrapped, nullptr);
Require(fseek_nid_postfix(wrapped, 0, SEEK_SET) == 0);
char contents[32]{};
Require(fgets_nid_postfix(contents, sizeof(contents), wrapped) == contents && std::strcmp(contents, "retained") == 0);
Require(fclose_nid_postfix(wrapped) == 0);
#ifdef _WIN32
Require(_close(descriptor) == -1);
#else
Require(::close(descriptor) == -1);
#endif
Require(std::fclose(original) == 0);
char stringOutput[256];
std::memset(stringOutput, '!', sizeof(stringOutput));
std::int64_t count = -1;
const char expectedString[] = "guest:4294967297: 3.50:1,2,3,4,5,6,7,8:%";
const int written = FormatString(stringOutput, "%s:%ld:%*.*f:%d,%d,%d,%d,%d,%d,%d,%d:%%%ln",
"guest", std::int64_t{4294967297}, 6, 2, 3.5, 1, 2, 3, 4, 5, 6, 7, 8, &count);
Require(written == sizeof(expectedString) - 1 && count == written);
Require(std::strcmp(stringOutput, expectedString) == 0 && stringOutput[written + 1] == '!');
Require(FormatString(stringOutput, "%.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f %.2Lf",
1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 1.25L) == 25);
Require(std::strcmp(stringOutput, "1 2 3 4 5 6 7 8 9 10 1.25") == 0);
Require(FormatString(stringOutput, "") == 0 && stringOutput[0] == '\0');
Require(__isthreaded_nid_postfix == 1);
Require(__stdoutp_nid_postfix == &_Stdout_nid_postfix);
Require(__stderrp_nid_postfix == &_Stderr_nid_postfix);
Require(fileno_nid_postfix(__stdinp_nid_postfix) == 0);
Require(fileno_nid_postfix(__stdoutp_nid_postfix) == 1);
Require(fileno_nid_postfix(__stderrp_nid_postfix) == 2);
FileStream stream(std::tmpfile());
auto& guest = *reinterpret_cast<GuestFilePrefix*>(&stream);
Require(&guest == &stream.GuestState());
Require(guest.position == nullptr && guest.readRemaining == 0 && guest.writeRemaining == 0);
Require(guest.descriptor == fileno_nid_postfix(&stream));
FileStream lineBuffered(std::tmpfile());
Require(setvbuf_nid_postfix(&lineBuffered, nullptr, 1, 0) == 0);
FileStream fullyBuffered(std::tmpfile());
Require(setvbuf_nid_postfix(&fullyBuffered, nullptr, 0, 0) == 0);
Require(setvbuf_nid_postfix(&stream, nullptr, 2, 0) == 0);
Require(fputc_nid_postfix('A', &stream) == 'A');
Require(putc_unlocked_nid_postfix('C', &stream) == 'C');
Require(--guest.writeRemaining < 0 && __swbuf_nid_postfix('\n', &stream) == '\n');
std::rewind(stream.GetHandle());
Require(--guest.readRemaining < 0 && __srget_nid_postfix(&stream) == 'A');
Require(getc_unlocked_nid_postfix(&stream) == 'C');
Require(ungetc_nid_postfix('B', &stream) == 'B');
char text[8]{};
Require(fgets_nid_postfix(text, sizeof(text), &stream) == text);
Require(std::strcmp(text, "B\n") == 0);
Require(fgetc_nid_postfix(&stream) == EOF);
Require(feof_nid_postfix(&stream) && (guest.flags & 0x20));
clearerr_nid_postfix(&stream);
Require(!feof_nid_postfix(&stream) && !(guest.flags & 0x20));
stream.Close();
Require(guest.flags == 0 && guest.descriptor == -1);
FileStream wide(std::tmpfile());
Require(fputwc_nid_postfix(u'A', &wide) == u'A');
Require(fputws_nid_postfix(u"B\x00E9", &wide) == 2);
std::rewind(wide.GetHandle());
char wideBytes[8]{};
Require(std::fread(wideBytes, 1, 4, wide.GetHandle()) == 4);
Require(std::memcmp(wideBytes, "AB\xC3\xA9", 4) == 0);
wide.Close();
FileStream formatted(std::tmpfile());
const char expected[] = "guest 4294967297 1.25 1 2 3 4 5 6 7 8\n";
Require(fprintf_nid_postfix(&formatted, "%s %ld %.2f %d %d %d %d %d %d %d %d\n",
"guest", std::int64_t{4294967297}, 1.25, 1, 2, 3, 4, 5, 6, 7, 8) == sizeof(expected) - 1);
Require(WriteFormatted(&formatted, "%*.*f:%s", 6, 2, 3.5, "end") == 10);
std::rewind(formatted.GetHandle());
char output[128]{};
Require(fgets_nid_postfix(output, sizeof(output), &formatted) == output);
Require(std::strcmp(output, expected) == 0);
Require(fgets_nid_postfix(output, sizeof(output), &formatted) == output);
Require(std::strcmp(output, " 3.50:end") == 0);
formatted.Close();
FileStream scanned(std::tmpfile());
Require(fprintf_nid_postfix(&scanned, "%d %s", 42, "answer") == 9);
std::rewind(scanned.GetHandle());
Require(fscanf_nid_postfix(&scanned, "%*d") == 0);
Require(ftello_nid_postfix(&scanned) == 2);
std::rewind(scanned.GetHandle());
int scannedNumber = 0;
char scannedWord[16]{};
std::fputs("fscanf: suppressed conversion passed; assigning register arguments\n", stderr);
std::fflush(stderr);
Require(fscanf_nid_postfix(&scanned, "%d %15s", &scannedNumber, scannedWord) == 2);
Require(scannedNumber == 42 && std::strcmp(scannedWord, "answer") == 0);
Require(fscanf_nid_postfix(&scanned, "%d", &scannedNumber) == EOF);
Require(feof_nid_postfix(&scanned) && (scanned.GuestState().flags & 0x20));
scanned.Close();
#ifdef _WIN32
FileStream scanSuppressed(std::tmpfile());
Require(std::fputs("12", scanSuppressed.GetHandle()) >= 0);
std::rewind(scanSuppressed.GetHandle());
int suppressedTarget = 321;
Require(fscanf_nid_postfix(&scanSuppressed, "%*d%d", &suppressedTarget) == 0 && suppressedTarget == 321);
Require(fscanf_nid_postfix(&scanSuppressed, "%*d") == EOF);
scanSuppressed.Close();
#endif
FileStream scanMany(std::tmpfile());
Require(std::fputs("7 1 2 3 4 5 6 7 8 4294967297 -4294967298 4294967299 abc %!", scanMany.GetHandle()) >= 0);
std::rewind(scanMany.GetHandle());
int numbers[8]{};
std::int64_t large = 0, negative = 0;
std::uint64_t sized = 0;
char letters[4]{};
std::int64_t consumed = -1;
Require(fscanf_nid_postfix(&scanMany, "%*d %d %d %d %d %d %d %d %d %ld %jd %zu %3[a-z] %%%ln",
&numbers[0], &numbers[1], &numbers[2], &numbers[3], &numbers[4], &numbers[5], &numbers[6], &numbers[7],
&large, &negative, &sized, letters, &consumed) == 12);
for (int i = 0; i < 8; ++i) Require(numbers[i] == i + 1);
Require(large == INT64_C(4294967297) && negative == -INT64_C(4294967298) && sized == UINT64_C(4294967299));
Require(std::strcmp(letters, "abc") == 0 && consumed == ftello_nid_postfix(&scanMany));
Require(fgetc_nid_postfix(&scanMany) == '!');
int unmatched = 123;
Require(fseeko_nid_postfix(&scanMany, -1, SEEK_CUR) == 0);
Require(fscanf_nid_postfix(&scanMany, "%d", &unmatched) == 0 && unmatched == 123);
Require(fgetc_nid_postfix(&scanMany) == '!');
Require(fscanf_nid_postfix(&scanMany, "%d", &unmatched) == EOF && unmatched == 123);
scanMany.Close();
FileStream positioned(std::tmpfile());
constexpr std::int64_t largeOffset = INT64_C(4294967313);
Require(fseeko_nid_postfix(&positioned, largeOffset, SEEK_SET) == 0);
Require(ftello_nid_postfix(&positioned) == largeOffset);
Require(ftell_nid_postfix(&positioned) == largeOffset);
Require(fseek_nid_postfix(&positioned, -9, SEEK_CUR) == 0);
Require(ftello_nid_postfix(&positioned) == largeOffset - 9);
Require(fseek_nid_postfix(&positioned, largeOffset, SEEK_SET) == 0);
Require(ftell_nid_postfix(&positioned) == largeOffset);
Require(fseeko_nid_postfix(&positioned, 0, 12345) == -1 && *__error_nid_postfix() == 22);
Require(ftello_nid_postfix(&positioned) == largeOffset);
Require(fseeko_nid_postfix(&positioned, 0, SEEK_END) == 0);
Require(ftello_nid_postfix(&positioned) == 0); // Seeking alone did not extend the file.
Require(fgetc_nid_postfix(&positioned) == EOF && feof_nid_postfix(&positioned));
Require(fseeko_nid_postfix(&positioned, 0, SEEK_SET) == 0);
Require(!feof_nid_postfix(&positioned));
positioned.Close();
const auto filename = "anyps5-reopen-test-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
FileStream redirected(std::tmpfile());
Require(fgetc_nid_postfix(&redirected) == EOF && feof_nid_postfix(&redirected));
Require(freopen_nid_postfix(filename.c_str(), "w+b", &redirected) == &redirected);
Require(!feof_nid_postfix(&redirected));
Require(fputc_nid_postfix('R', &redirected) == 'R');
Require(fseeko_nid_postfix(&redirected, 0, SEEK_SET) == 0);
Require(fgetc_nid_postfix(&redirected) == 'R');
Require(freopen_nid_postfix(filename.c_str(), "ab", &redirected) == &redirected);
Require(fputc_nid_postfix('S', &redirected) == 'S');
Require(freopen_nid_postfix(nullptr, "r", &redirected) == nullptr && *__error_nid_postfix() == 45);
redirected.Close();
{ std::ifstream input(filename, std::ios::binary); std::string contents; std::getline(input, contents);
Require(contents == "RS"); }
Require(std::filesystem::remove(filename));
FileStream failed(std::tmpfile());
Require(freopen_nid_postfix(filename.c_str(), "rb", &failed) == nullptr);
Require(*__error_nid_postfix() == 2);
Require(failed.GuestState().flags == 0 && failed.GuestState().descriptor == -1);
FileStream locked(std::tmpfile());
flockfile_nid_postfix(&locked);
flockfile_nid_postfix(&locked);
std::atomic<bool> lockedWrite = false;
std::thread writer([&] {
Require(fputc_nid_postfix('B', &locked) == 'B');
lockedWrite = true;
});
std::this_thread::sleep_for(std::chrono::milliseconds(50));
Require(!lockedWrite && fputc_nid_postfix('A', &locked) == 'A');
funlockfile_nid_postfix(&locked);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
Require(!lockedWrite);
funlockfile_nid_postfix(&locked);
writer.join();
Require(lockedWrite && fseeko_nid_postfix(&locked, 0, SEEK_SET) == 0);
Require(fgetc_nid_postfix(&locked) == 'A' && fgetc_nid_postfix(&locked) == 'B');
locked.Close();
CheckRewind();
CheckUnlockedStreams();
CheckUnlockedStreamLock(false);
CheckUnlockedStreamLock(true);
CheckTryLock();
return CheckBinaryModes() ? 0 : 1;
}