Files
AnyPS5/core/libs/tests/GuestStandardStreams.cpp
T
Evrim Cicek a7b52084ec fix(libc): set up streams for writing after a read as FreeBSD does on Windows
On Windows the UCRT drops a write that directly follows a read on an r+,
w+ or a+ stream: fputc and fprintf report success, fwrite returns 0 and
fputs returns EOF, all without the error indicator, and the file is left
unchanged, also after an fflush. On a stream open only for reading, fputc
after a read reports success as well.

Writes now first do what FreeBSD 11.4's __swsetup does: a stream open only
for reading fails with EBADF and sets the error indicator, and an update
stream that is reading drops its read buffer and ungetc data, clears end
of file and writes at the descriptor's offset. This covers fwrite, fputs,
fputc, putc_unlocked, fprintf, fprintf_s, fputwc, fputws, fwprintf,
fwprintf_s, puts and wprintf, and printf, vprintf and printf_s, which
write to standard output through PrintWindows and now return -1 there
instead of throwing.

Linux is unchanged: glibc writes at the stream position, which the libc
technical debt now records.
2026-10-26 21:53:56 +03:00

1002 lines
48 KiB
C++

#include "prx/libc/include/Api.hpp"
#include "prx/libc/include/FileStream.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <climits>
#include <cstdlib>
#include <fcntl.h>
#include <cstring>
#include <cstdarg>
#include <cstdint>
#include <string_view>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <initializer_list>
#include <limits>
#include <chrono>
#include <atomic>
#include <thread>
#include <stdexcept>
#ifdef _WIN32
#include <fcntl.h>
#include <windows.h>
#else
#include <unistd.h>
#endif
extern "C" {
extern FileStream* __stdinp_nid_postfix;
extern FileStream* __stdoutp_nid_postfix;
extern FileStream* __stderrp_nid_postfix;
extern int __isthreaded_nid_postfix;
}
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, reinterpret_cast<VaList*>(args));
#ifdef _WIN32
__builtin_sysv_va_end(args);
#else
va_end(args);
#endif
return result;
}
static int APS5_VABI PrintStandard(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 = vprintf_nid_postfix(format, reinterpret_cast<VaList*>(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, reinterpret_cast<VaList*>(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.SetError();
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));
Require(ferror_unlocked_nid_postfix(&readOnly) == 1 && feof_unlocked_nid_postfix(&readOnly) == 0);
clearerr_unlocked_nid_postfix(&readOnly);
Require(ferror_unlocked_nid_postfix(&readOnly) == 0);
readOnly.Close();
Require(CheckFileBytes(filename, "A", "rb", false));
Require(std::filesystem::remove(filename));
}
static void CheckUnlockedQueries() {
FileStream stream(std::tmpfile());
Require(feof_unlocked_nid_postfix(&stream) == 0 && ferror_unlocked_nid_postfix(&stream) == 0);
Require(std::fgetc(stream.GetHandle()) == EOF);
Require(feof_unlocked_nid_postfix(&stream) == 1 && ferror_unlocked_nid_postfix(&stream) == 0);
Require((stream.GuestState().flags & 0x60) == 0x20);
stream.SetError();
Require(feof_unlocked_nid_postfix(&stream) == 1 && ferror_unlocked_nid_postfix(&stream) == 1);
clearerr_unlocked_nid_postfix(&stream);
Require(feof_unlocked_nid_postfix(&stream) == 0 && ferror_unlocked_nid_postfix(&stream) == 0);
Require(std::fgetc(stream.GetHandle()) == EOF && std::ungetc('Q', stream.GetHandle()) == 'Q');
Require(feof_unlocked_nid_postfix(&stream) == 0 && ferror_unlocked_nid_postfix(&stream) == 0);
Require(std::fgetc(stream.GetHandle()) == 'Q');
Require(fputc_nid_postfix(0x7f, &stream) == 0x7f);
std::rewind(stream.GetHandle());
Require(fgetc_nid_postfix(&stream) == 0x7f && feof_nid_postfix(&stream) == 0);
Require(fgetc_nid_postfix(&stream) == -1 && feof_nid_postfix(&stream) != 0);
clearerr_unlocked_nid_postfix(&stream);
Require(feof_nid_postfix(&stream) == 0 && ferror_nid_postfix(&stream) == 0);
Require(fileno_unlocked_nid_postfix(&stream) == fileno_nid_postfix(&stream) && fileno_unlocked_nid_postfix(&stream) >= 0);
Require(fileno_unlocked_nid_postfix(nullptr) == -1);
bool halted = false;
std::size_t length = 0;
try { fgetln_nid_postfix(&stream, &length); } catch (const std::runtime_error&) { halted = true; }
Require(halted);
}
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, int (APS5_VABI *query)(FileStream*) = nullptr) {
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 = query ? query(&stream) : 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 == (query ? 0 : write ? 'B' : 'A'));
stream.Close();
}
static void CheckFgetsSize() {
FileStream stream(std::tmpfile());
Require(fwrite_nid_postfix("line\n", 1, 5, &stream) == 5);
rewind_nid_postfix(&stream);
for (const int size : {0, -1, std::numeric_limits<int>::min()}) {
char buffer[8];
std::memset(buffer, '!', sizeof(buffer));
*__error_nid_postfix() = 0;
Require(fgets_nid_postfix(buffer, size, &stream) == nullptr);
Require(*__error_nid_postfix() == 22 && ferror_nid_postfix(&stream) != 0 && feof_nid_postfix(&stream) == 0);
Require(buffer[0] == '!' && ftello_nid_postfix(&stream) == 0);
Require(ferror_nid_postfix(&stream) != 0);
clearerr_nid_postfix(&stream);
Require(ferror_nid_postfix(&stream) == 0);
}
char line[8]{};
Require(fgets_nid_postfix(line, sizeof(line), &stream) == line && std::strcmp(line, "line\n") == 0);
Require(ferror_nid_postfix(&stream) == 0);
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.SetError();
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();
int pipeEnds[2];
#ifdef _WIN32
Require(_pipe(pipeEnds, 4096, _O_BINARY) == 0 && _write(pipeEnds[1], "pq", 2) == 2 && _close(pipeEnds[1]) == 0);
FileStream pipe(_fdopen(pipeEnds[0], "rb"));
#else
Require(::pipe(pipeEnds) == 0);
Require(::write(pipeEnds[1], "pq", 2) == 2);
::close(pipeEnds[1]);
FileStream pipe(::fdopen(pipeEnds[0], "r"));
#endif
Require(fgetc_nid_postfix(&pipe) == 'p');
for (const int origin : {SEEK_SET, SEEK_CUR, SEEK_END}) {
*__error_nid_postfix() = 0;
Require(fseeko_nid_postfix(&pipe, 0, origin) == -1 && *__error_nid_postfix() == 29);
}
*__error_nid_postfix() = 0;
Require(ftello_nid_postfix(&pipe) == -1 && *__error_nid_postfix() == 29 && ferror_nid_postfix(&pipe) == 0);
Require(fgetc_nid_postfix(&pipe) == 'q' && 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();
}
static void CheckFlushWhileReading() {
const auto filename = "anyps5-flush-read-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
{ std::ofstream seed(filename, std::ios::binary); seed << "abcdef\n"; Require(seed.good()); }
for (const char* mode : {"r", "r+"}) {
FileStream* stream = fopen_nid_postfix(filename.c_str(), mode);
Require(stream != nullptr);
Require(fgetc_nid_postfix(stream) == 'a' && ungetc_nid_postfix('Z', stream) == 'Z');
Require(fflush_nid_postfix(stream) == 0);
char rest[16]{};
const auto count = fread_nid_postfix(rest, 1, sizeof(rest) - 1, stream);
if (count != 7 || std::strcmp(rest, "Zbcdef\n") != 0) {
std::string shown;
for (std::size_t index = 0; index < count; ++index) shown += rest[index] == '\n' ? std::string("\\n") : std::string(1, rest[index]);
std::fprintf(stderr, "fflush of a \"%s\" stream that is reading changed the data read next: \"%s\" (%zu bytes), expected \"Zbcdef\\n\" (7 bytes)\n",
mode, shown.c_str(), count);
std::abort();
}
Require(fclose_nid_postfix(stream) == 0);
}
std::filesystem::remove(filename);
}
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 == 'a') {
const bool atEnd = ftello_nid_postfix(stream) == static_cast<std::int64_t>(original.size()) &&
(!update || fgetc_nid_postfix(stream) == EOF);
if (!atEnd) std::fprintf(stderr, "%s %s: the append stream does not start at the end of the file\n", reopen ? "freopen" : "fopen", mode);
correct &= atEnd;
}
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;
}
static int DuplicateDescriptor(std::FILE* stream) {
#ifdef _WIN32
return _dup(_fileno(stream));
#else
return ::dup(::fileno(stream));
#endif
}
static void CheckDescriptorModes(std::FILE* original) {
for (const char* mode : {"rt", "r+t", "re", "rbe", "rw", "r+x"}) {
const int descriptor = DuplicateDescriptor(original);
Require(descriptor >= 0);
auto* wrapped = fdopen_nid_postfix(descriptor, mode);
Require(wrapped != nullptr && 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);
}
const int descriptor = DuplicateDescriptor(original);
Require(descriptor >= 0);
*__error_nid_postfix() = 0;
Require(fdopen_nid_postfix(descriptor, "rx") == nullptr && *__error_nid_postfix() == 22);
#ifdef _WIN32
Require(_close(descriptor) == 0);
#else
Require(::close(descriptor) == 0);
#endif
}
static void CheckDescriptorAppend(std::FILE* original) {
std::string expected = "retained";
for (const char* mode : {"a", "ab", "a+", "ae"}) {
const int descriptor = DuplicateDescriptor(original);
#ifdef _WIN32
Require(descriptor >= 0 && _lseek(descriptor, 2, SEEK_SET) == 2);
#else
Require(descriptor >= 0 && ::lseek(descriptor, 2, SEEK_SET) == 2);
#endif
auto* wrapped = fdopen_nid_postfix(descriptor, mode);
Require(wrapped != nullptr && fileno_nid_postfix(wrapped) == descriptor);
Require(fputc_nid_postfix('+', wrapped) == '+' && fflush_nid_postfix(wrapped) == 0);
Require(fseek_nid_postfix(wrapped, 0, SEEK_SET) == 0 && fputc_nid_postfix('+', wrapped) == '+');
Require(fclose_nid_postfix(wrapped) == 0);
expected += "++";
const int reader = DuplicateDescriptor(original);
Require(reader >= 0);
auto* written = fdopen_nid_postfix(reader, "r");
Require(written != nullptr && fseek_nid_postfix(written, 0, SEEK_SET) == 0);
char contents[32]{};
Require(fgets_nid_postfix(contents, sizeof(contents), written) == contents && expected == contents);
Require(fclose_nid_postfix(written) == 0);
}
}
static void CheckDescriptorAccessModes() {
const auto filename = "anyps5-fdopen-modes-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
for (const int access : {0, 1, 2}) {
for (const char* mode : {"r", "w", "a", "r+", "w+", "a+"}) {
{ std::ofstream output(filename); output << "retained"; }
#ifdef _WIN32
const int descriptor = _open(filename.c_str(), access | _O_BINARY);
#else
const int descriptor = ::open(filename.c_str(), access);
#endif
Require(descriptor >= 0);
const int requested = std::strchr(mode, '+') ? 2 : *mode == 'r' ? 0 : 1;
#ifdef _WIN32
const bool incompatible = false;
const bool unchecked = access != 2 && access != requested;
#else
const bool incompatible = access != 2 && access != requested;
const bool unchecked = false;
#endif
#ifdef _WIN32
const auto originalHandle = _get_osfhandle(descriptor);
if (incompatible) Require(_lseek(descriptor, 2, SEEK_SET) == 2);
#else
const int originalFlags = ::fcntl(descriptor, F_GETFL);
Require(originalFlags >= 0);
if (incompatible) Require(::lseek(descriptor, 2, SEEK_SET) == 2);
#endif
*__error_nid_postfix() = 0;
auto* stream = fdopen_nid_postfix(descriptor, mode);
if (incompatible) {
if (stream != nullptr || *__error_nid_postfix() != 22)
std::fprintf(stderr, "fdopen access=%d mode=%s returned %p with errno=%d\n", access, mode,
static_cast<void*>(stream), *__error_nid_postfix());
Require(stream == nullptr && *__error_nid_postfix() == 22);
#ifdef _WIN32
Require(_get_osfhandle(descriptor) == originalHandle && _lseek(descriptor, 0, SEEK_CUR) == 2);
#else
Require(::fcntl(descriptor, F_GETFL) == originalFlags && ::lseek(descriptor, 0, SEEK_CUR) == 2);
#endif
Require(CheckFileBytes(filename, "retained", mode, false));
const bool probeAppend = access == 1 && *mode == 'a';
#ifdef _WIN32
if (probeAppend) Require(_write(descriptor, "X", 1) == 1 && _lseek(descriptor, 0, SEEK_CUR) == 3);
Require(_close(descriptor) == 0);
#else
if (probeAppend) Require(::write(descriptor, "X", 1) == 1 && ::lseek(descriptor, 0, SEEK_CUR) == 3);
Require(::close(descriptor) == 0);
#endif
Require(CheckFileBytes(filename, probeAppend ? "reXained" : "retained", mode, false));
} else if (unchecked) {
Require(stream != nullptr && fileno_nid_postfix(stream) == descriptor);
if (access == 1) {
Require(fgetc_nid_postfix(stream) == -1 && ferror_nid_postfix(stream) != 0);
} else {
Require(fputc_nid_postfix('X', stream) == 'X');
Require(fflush_nid_postfix(stream) == -1 && *__error_nid_postfix() == 9);
}
fclose_nid_postfix(stream);
Require(CheckFileBytes(filename, "retained", mode, false));
} else {
Require(stream != nullptr);
if (requested == 0) Require(fgetc_nid_postfix(stream) == 'r');
else Require(fputc_nid_postfix('X', stream) == 'X');
Require(fclose_nid_postfix(stream) == 0);
}
}
}
#ifdef _WIN32
const int closed = _open(filename.c_str(), _O_RDONLY | _O_BINARY);
Require(closed >= 0 && _close(closed) == 0);
#else
const int closed = ::open(filename.c_str(), O_RDONLY);
Require(closed >= 0 && ::close(closed) == 0);
#endif
*__error_nid_postfix() = 0;
Require(fdopen_nid_postfix(closed, "r") == nullptr && *__error_nid_postfix() == 9);
Require(std::filesystem::remove(filename));
}
static void CheckClosedStreamWrites() {
FileStream stream(std::tmpfile());
Require(freopen_nid_postfix("missing-closed-stream-dir/out.log", "w", &stream) == nullptr);
Require(fputs_nid_postfix("text", &stream) == -1 && *__error_nid_postfix() == 9);
Require(fprintf_nid_postfix(&stream, "%d", 1) == -1 && *__error_nid_postfix() == 9);
Require(fwrite_nid_postfix("text", 1, 4, &stream) == 0 && *__error_nid_postfix() == 9);
Require(fputc_nid_postfix('t', &stream) == -1 && *__error_nid_postfix() == 9);
Require(putc_unlocked_nid_postfix('t', &stream) == -1 && *__error_nid_postfix() == 9);
Require(fflush_nid_postfix(&stream) == -1 && *__error_nid_postfix() == 9);
char buffer[8] = {};
Require(fread_nid_postfix(buffer, 1, sizeof(buffer), &stream) == 0 && *__error_nid_postfix() == 9);
Require(fgetc_nid_postfix(&stream) == -1 && *__error_nid_postfix() == 9);
Require(fgets_nid_postfix(buffer, sizeof(buffer), &stream) == nullptr && *__error_nid_postfix() == 9);
Require(fseeko_nid_postfix(&stream, 0, SEEK_SET) == -1 && *__error_nid_postfix() == 9);
Require(ftello_nid_postfix(&stream) == -1 && *__error_nid_postfix() == 9);
Require(fileno_nid_postfix(&stream) == -1 && *__error_nid_postfix() == 9);
*__error_nid_postfix() = 0;
Require(fputwc_nid_postfix(u't', &stream) == -1 && *__error_nid_postfix() == 9);
*__error_nid_postfix() = 0;
Require(putwc_nid_postfix(u't', &stream) == -1 && *__error_nid_postfix() == 9);
*__error_nid_postfix() = 0;
Require(fputws_nid_postfix(u"text", &stream) == -1 && *__error_nid_postfix() == 9);
*__error_nid_postfix() = 0;
Require(ungetwc_nid_postfix('t', &stream) == -1 && *__error_nid_postfix() == 9);
*__error_nid_postfix() = 0;
Require(ungetwc_nid_postfix(0x100, &stream) == -1 && *__error_nid_postfix() == 9);
Require(ferror_nid_postfix(&stream) == 0 && feof_nid_postfix(&stream) == 0);
Require(fclose_nid_postfix(&stream) == -1 && *__error_nid_postfix() == 9);
}
static int CheckClosedStandardOutput() {
Require(freopen_nid_postfix("missing-closed-stream-dir/out.log", "w", __stdoutp_nid_postfix) == nullptr);
Require(printf_nid_postfix("%d", 1) == -1 && *__error_nid_postfix() == 9);
Require(puts_nid_postfix("text") == -1 && *__error_nid_postfix() == 9);
Require(PrintStandard("%d", 1) == -1 && *__error_nid_postfix() == 9);
Require(fputs_nid_postfix("text", __stdoutp_nid_postfix) == -1 && *__error_nid_postfix() == 9);
*__error_nid_postfix() = 0;
Require(wprintf_nid_postfix(u"%d", 1) == -1 && *__error_nid_postfix() == 9);
*__error_nid_postfix() = 0;
Require(putwchar_nid_postfix(u't') == -1 && *__error_nid_postfix() == 9);
Require(std::fputs("host output\n", stdout) >= 0 && std::fflush(stdout) == 0 && !std::ferror(stdout));
Require(freopen_nid_postfix("missing-closed-stream-dir/err.log", "a", __stderrp_nid_postfix) == nullptr);
Require(std::fputs("host error\n", stderr) >= 0 && std::fflush(stderr) == 0 && !std::ferror(stderr));
return 0;
}
static int CheckExclusiveStandardOutput() {
const auto filename = "anyps5-exclusive-stdout-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
Require(freopen_nid_postfix(filename.c_str(), "wx", __stdoutp_nid_postfix) == __stdoutp_nid_postfix);
Require(fputs_nid_postfix("exclusive\n", __stdoutp_nid_postfix) >= 0 && fflush_nid_postfix(__stdoutp_nid_postfix) == 0);
Require(fclose_nid_postfix(__stdoutp_nid_postfix) == 0);
Require(CheckFileBytes(filename, "exclusive\n", "wx", true));
Require(std::filesystem::remove(filename));
return 0;
}
static int CheckUpdateStandardOutput() {
const auto filename = "anyps5-update-stdout-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
auto* const stream = __stdoutp_nid_postfix;
Require(freopen_nid_postfix(filename.c_str(), "a+b", stream) == stream);
Require(fputs_nid_postfix("0123456789", stream) >= 0);
const auto readFirst = [stream] { Require(fseek_nid_postfix(stream, 0, SEEK_SET) == 0 && fgetc_nid_postfix(stream) == '0'); };
readFirst();
Require(puts_nid_postfix("A") != EOF);
readFirst();
Require(printf_nid_postfix("%s", "B") == 1);
readFirst();
Require(PrintStandard("%s", "C") == 1);
readFirst();
Require(printf_s_nid_postfix("%s", "D") == 1);
readFirst();
Require(wprintf_nid_postfix(u"E") == 1);
Require(ferror_nid_postfix(stream) == 0);
Require(freopen_nid_postfix(filename.c_str(), "rb", stream) == stream && fgetc_nid_postfix(stream) == '0');
*__error_nid_postfix() = 0;
Require(puts_nid_postfix("A") == EOF && *__error_nid_postfix() == 9);
*__error_nid_postfix() = 0;
Require(printf_nid_postfix("%s", "B") == -1 && *__error_nid_postfix() == 9);
*__error_nid_postfix() = 0;
Require(PrintStandard("%s", "C") == -1 && *__error_nid_postfix() == 9);
*__error_nid_postfix() = 0;
Require(printf_s_nid_postfix("%s", "D") == -1 && *__error_nid_postfix() == 9);
*__error_nid_postfix() = 0;
Require(wprintf_nid_postfix(u"E") == -1 && *__error_nid_postfix() == 9);
Require(ferror_nid_postfix(stream) != 0 && fclose_nid_postfix(stream) == 0);
Require(CheckFileBytes(filename, "0123456789A\nBCDE", "a+b", true));
Require(std::filesystem::remove(filename));
return 0;
}
#ifdef _WIN32
static void CheckStandardStreamDeleteSharing() {
int index = 0;
for (auto* stream : {__stdinp_nid_postfix, __stderrp_nid_postfix}) {
const std::string filename = "anyps5-standard-delete-" + std::to_string(index++) + "-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
auto* const native = stream->GetHandle();
const int descriptor = ::_fileno(native);
Require(freopen_nid_postfix(filename.c_str(), "w+b", stream) == stream);
Require(stream->GetHandle() == native && ::_fileno(stream->GetHandle()) == descriptor);
Require(fputc_nid_postfix('A', stream) == 'A' && fflush_nid_postfix(stream) == 0);
const std::string renamed = filename + "-moved";
std::filesystem::rename(filename, renamed);
Require(fputc_nid_postfix('B', stream) == 'B' && fflush_nid_postfix(stream) == 0);
Require(std::filesystem::remove(renamed));
Require(fseeko_nid_postfix(stream, 0, SEEK_SET) == 0);
Require(fgetc_nid_postfix(stream) == 'A' && fgetc_nid_postfix(stream) == 'B');
}
}
#endif
int main(int argc, char** argv) {
if (argc > 1 && std::string_view(argv[1]) == "--update-stdout") return CheckUpdateStandardOutput();
if (argc > 1 && std::string_view(argv[1]) == "--closed-stdout") return CheckClosedStandardOutput();
if (argc > 1 && std::string_view(argv[1]) == "--exclusive-stdout") return CheckExclusiveStandardOutput();
CheckClosedStreamWrites();
CheckDescriptorAccessModes();
Require(_Getmbcurmax_nid_postfix() == 3 && ___mb_cur_max_nid_postfix() == 1);
CheckFlushWhileReading();
for (const int negative : {-1, -2, INT_MIN}) {
*__error_nid_postfix() = 1234;
Require(fdopen_nid_postfix(negative, "rb") == nullptr && *__error_nid_postfix() == 1234);
for (const char* invalid : {"x", "", "z", "xr"}) {
*__error_nid_postfix() = 1234;
Require(fdopen_nid_postfix(negative, invalid) == nullptr && *__error_nid_postfix() == 22);
}
}
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
CheckDescriptorModes(original);
CheckDescriptorAppend(original);
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);
auto* const savedInput = __stdinp_nid_postfix;
auto* const savedOutput = __stdoutp_nid_postfix;
FileStream customIn(std::tmpfile());
Require(fputc_nid_postfix('T', &customIn) == 'T');
std::rewind(customIn.GetHandle());
__stdinp_nid_postfix = &customIn;
Require(getchar_unlocked_nid_postfix() == 'T');
Require(getchar_unlocked_nid_postfix() == EOF);
Require(ungetc_nid_postfix('W', &customIn) == 'W');
Require(getwchar_nid_postfix() == 'W');
FileStream customOut(std::tmpfile());
__stdoutp_nid_postfix = &customOut;
Require(putchar_unlocked_nid_postfix('U') == 'U');
Require(putwchar_nid_postfix(u'V') == u'V');
std::rewind(customOut.GetHandle());
Require(fgetc_nid_postfix(&customOut) == 'U');
Require(fgetc_nid_postfix(&customOut) == 'V');
__stdinp_nid_postfix = savedInput;
__stdoutp_nid_postfix = savedOutput;
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 wideIo(std::tmpfile());
Require(putwc_nid_postfix(u'H', &wideIo) == u'H');
Require(putwc_nid_postfix(u'i', &wideIo) == u'i');
Require(putwc_nid_postfix(u'\n', &wideIo) == u'\n');
Require(fputws_nid_postfix(u"Next\n", &wideIo) == 5);
Require(fputws_nid_postfix(u"End", &wideIo) == 3);
std::rewind(wideIo.GetHandle());
Require(getwc_nid_postfix(&wideIo) == 'H');
std::rewind(wideIo.GetHandle());
char16_t wideBuf[16]{};
Require(fgetws_nid_postfix(wideBuf, 16, &wideIo) == wideBuf);
Require(wideBuf[0] == u'H' && wideBuf[1] == u'i' && wideBuf[2] == u'\n' && wideBuf[3] == 0);
Require(fgetws_nid_postfix(wideBuf, 16, &wideIo) == wideBuf);
Require(wideBuf[0] == u'N' && wideBuf[1] == u'e' && wideBuf[2] == u'x' && wideBuf[3] == u't' && wideBuf[4] == u'\n' && wideBuf[5] == 0);
Require(fgetws_nid_postfix(wideBuf, 16, &wideIo) == wideBuf);
Require(wideBuf[0] == u'E' && wideBuf[1] == u'n' && wideBuf[2] == u'd' && wideBuf[3] == 0);
Require(fgetws_nid_postfix(wideBuf, 16, &wideIo) == nullptr);
Require(feof_nid_postfix(&wideIo));
clearerr_nid_postfix(&wideIo);
*__error_nid_postfix() = 0;
Require(fgetws_nid_postfix(wideBuf, 0, &wideIo) == nullptr && *__error_nid_postfix() == 0);
Require(fgetws_nid_postfix(wideBuf, -1, &wideIo) == nullptr && *__error_nid_postfix() == 0);
Require(!ferror_nid_postfix(&wideIo));
Require(fgetws_nid_postfix(nullptr, 16, &wideIo) == nullptr && *__error_nid_postfix() == 22);
Require(ferror_nid_postfix(&wideIo));
clearerr_nid_postfix(&wideIo);
Require(fgetws_nid_postfix(wideBuf, 16, nullptr) == nullptr && *__error_nid_postfix() == 22);
wideBuf[0] = u'X';
wideBuf[1] = u'Y';
std::rewind(wideIo.GetHandle());
Require(fgetws_nid_postfix(wideBuf, 1, &wideIo) == nullptr && wideBuf[0] == u'X' && wideBuf[1] == u'Y');
Require(fgetc_nid_postfix(&wideIo) == 'H');
wideIo.Close();
FileStream roundTrip(std::tmpfile());
Require(putwc_nid_postfix(u'é', &roundTrip) == u'é');
Require(putwc_nid_postfix(u'\u20ac', &roundTrip) == u'\u20ac');
Require(putwc_nid_postfix(u'\n', &roundTrip) == u'\n');
std::rewind(roundTrip.GetHandle());
Require(getwc_nid_postfix(&roundTrip) == static_cast<std::int32_t>(u'é'));
Require(getwc_nid_postfix(&roundTrip) == static_cast<std::int32_t>(u'\u20ac'));
Require(getwc_nid_postfix(&roundTrip) == static_cast<std::int32_t>(u'\n'));
Require(getwc_nid_postfix(&roundTrip) == -1);
std::rewind(roundTrip.GetHandle());
char16_t utfBuf[16]{};
Require(fgetws_nid_postfix(utfBuf, 16, &roundTrip) == utfBuf);
Require(utfBuf[0] == u'é' && utfBuf[1] == u'\u20ac' && utfBuf[2] == u'\n' && utfBuf[3] == 0);
roundTrip.Close();
FileStream midErr(std::tmpfile());
Require(fputc_nid_postfix('A', &midErr) == 'A');
Require(fputc_nid_postfix(0xff, &midErr) == 0xff);
std::rewind(midErr.GetHandle());
char16_t midBuf[16]{};
Require(fgetws_nid_postfix(midBuf, 16, &midErr) == midBuf && midBuf[0] == u'A' && midBuf[1] == 0);
Require(ferror_nid_postfix(&midErr));
midErr.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();
FileStream scanBinary(std::tmpfile());
Require(std::fputs("0b101", scanBinary.GetHandle()) >= 0);
std::rewind(scanBinary.GetHandle());
int binaryValue = -1;
Require(fscanf_nid_postfix(&scanBinary, "%i", &binaryValue) == 1 && binaryValue == 0);
Require(fgetc_nid_postfix(&scanBinary) == 'b');
scanBinary.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();
CheckFgetsSize();
CheckUnlockedStreams();
CheckUnlockedStreamLock(false);
CheckUnlockedStreamLock(true);
CheckUnlockedStreamLock(false, feof_unlocked_nid_postfix);
CheckUnlockedStreamLock(false, ferror_unlocked_nid_postfix);
CheckTryLock();
CheckUnlockedQueries();
if (!CheckBinaryModes()) return 1;
#ifdef _WIN32
CheckStandardStreamDeleteSharing();
const std::string standardFilename = "anyps5-standard-reopen-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
Require(freopen_nid_postfix(standardFilename.c_str(), "w", __stdoutp_nid_postfix) == __stdoutp_nid_postfix);
const HANDLE hostOutput = ::GetStdHandle(STD_OUTPUT_HANDLE);
Require(hostOutput != nullptr && hostOutput != INVALID_HANDLE_VALUE);
Require(hostOutput != reinterpret_cast<HANDLE>(::_get_osfhandle(::_fileno(stdout))));
DWORD handleFlags = 0;
Require(::GetHandleInformation(hostOutput, &handleFlags) != 0);
constexpr char hostMessage[] = "host stdout preserved\n";
DWORD hostWritten = 0;
Require(::WriteFile(hostOutput, hostMessage, sizeof(hostMessage) - 1, &hostWritten, nullptr) != 0 && hostWritten == sizeof(hostMessage) - 1);
Require(printf_nid_postfix("standard output") == 15 && fflush_nid_postfix(__stdoutp_nid_postfix) == 0);
{ std::ifstream input(standardFilename, std::ios::binary); std::string contents; std::getline(input, contents);
Require(contents == "standard output"); }
const std::string movedFilename = standardFilename + "-moved";
std::error_code renameError;
std::filesystem::rename(standardFilename, movedFilename, renameError);
Require(!renameError && !std::filesystem::exists(standardFilename) && std::filesystem::exists(movedFilename));
Require(printf_nid_postfix(" continued") == 10 && fflush_nid_postfix(__stdoutp_nid_postfix) == 0);
{ std::ifstream input(movedFilename, std::ios::binary); std::string contents; std::getline(input, contents);
Require(contents == "standard output continued"); }
Require(std::filesystem::remove(movedFilename));
Require(!std::filesystem::exists(movedFilename));
Require(printf_nid_postfix(" after delete") == 13 && fflush_nid_postfix(__stdoutp_nid_postfix) == 0);
const std::string exclusiveFilename = "anyps5-standard-exclusive-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
{ std::ofstream out(exclusiveFilename, std::ios::binary); out << "existing"; }
Require(freopen_nid_postfix(exclusiveFilename.c_str(), "wx", __stdoutp_nid_postfix) == nullptr && *__error_nid_postfix() == 17);
Require(std::filesystem::remove(exclusiveFilename));
Require(printf_nid_postfix("closed") == -1 && *__error_nid_postfix() == 9);
Require(std::fputs("host stdout intact\n", stdout) >= 0 && std::fflush(stdout) == 0);
#endif
return 0;
}