#include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include #include #include #include #include #include extern "C" { int APS5_VABI getw_nid_postfix(FileStream*); int APS5_VABI putw_nid_postfix(int, FileStream*); FileStream* APS5_VABI fopen_nid_postfix(const char*, const char*); int APS5_VABI fclose_nid_postfix(FileStream*); 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*); int APS5_VABI fseek_nid_postfix(FileStream*, std::int64_t, int); std::int64_t APS5_VABI ftell_nid_postfix(FileStream*); int APS5_VABI ungetc_nid_postfix(int, FileStream*); int APS5_VABI fgetc_nid_postfix(FileStream*); void APS5_VABI clearerr_nid_postfix(FileStream*); void APS5_VABI flockfile_nid_postfix(FileStream*); void APS5_VABI funlockfile_nid_postfix(FileStream*); } namespace { void Require(bool condition, const char* message) { if (!condition) { std::fprintf(stderr, "binary word I/O: %s\n", message); std::abort(); } } void CheckRoundTrip(const char* path) { constexpr std::array values{0, 1, -1, std::numeric_limits::min(), std::numeric_limits::max(), 0x12345678, 0x0d0a1a00, -1234567}; auto* stream = fopen_nid_postfix(path, "w+b"); Require(stream != nullptr, "open round-trip file"); for (const int value : values) Require(putw_nid_postfix(value, stream) == 0, "putw returns zero on success"); Require(ftell_nid_postfix(stream) == static_cast(4 * values.size()), "write exactly four bytes per word"); Require(fseek_nid_postfix(stream, 0, SEEK_SET) == 0, "rewind written words"); std::array bytes; Require(fread_nid_postfix(bytes.data(), 1, bytes.size(), stream) == bytes.size(), "read raw word bytes"); for (std::size_t word = 0; word < values.size(); ++word) for (unsigned byte = 0; byte < 4; ++byte) Require(bytes[4 * word + byte] == ((static_cast(values[word]) >> (8 * byte)) & 0xff), "guest little-endian representation"); Require(fseek_nid_postfix(stream, 0, SEEK_SET) == 0, "rewind for getw"); for (const int value : values) { Require(getw_nid_postfix(stream) == value, "read signed word including minus one"); Require((stream->GuestState().flags & 0x60) == 0, "a stored minus one is not EOF or an error"); } Require(getw_nid_postfix(stream) == -1, "EOF returns minus one"); Require((stream->GuestState().flags & 0x60) == 0x20, "publish EOF without an error"); Require(fseek_nid_postfix(stream, 20, SEEK_SET) == 0, "seek to a buffered word"); Require(fgetc_nid_postfix(stream) == 0x78, "read first word byte"); Require(ungetc_nid_postfix(0x78, stream) == 0x78, "push back first word byte"); Require(getw_nid_postfix(stream) == 0x12345678, "getw consumes pushed-back bytes"); Require(ftell_nid_postfix(stream) == 24, "position after pushed-back word"); Require(fclose_nid_postfix(stream) == 0, "close round-trip file"); } void CheckShortReads(const char* path) { constexpr std::array bytes{0xff, 0xaa, 0xbb}; for (std::size_t length = 0; length <= bytes.size(); ++length) { auto* stream = fopen_nid_postfix(path, "w+b"); Require(stream != nullptr, "open short word file"); Require(fwrite_nid_postfix(bytes.data(), 1, length, stream) == length, "write incomplete word"); Require(fseek_nid_postfix(stream, 0, SEEK_SET) == 0, "rewind incomplete word"); Require(getw_nid_postfix(stream) == -1, "incomplete word is EOF"); Require((stream->GuestState().flags & 0x60) == 0x20, "short-read EOF flag"); Require(ftell_nid_postfix(stream) == static_cast(length), "consume incomplete word bytes"); clearerr_nid_postfix(stream); Require((stream->GuestState().flags & 0x60) == 0, "clear short-read status"); Require(fclose_nid_postfix(stream) == 0, "close incomplete word"); } } void CheckErrors(const char* path) { auto* writer = fopen_nid_postfix(path, "wb"); Require(writer != nullptr, "open write-only file"); Require(getw_nid_postfix(writer) == -1, "read on write-only stream fails"); Require((writer->GuestState().flags & 0x60) == 0x40, "publish read error, not EOF"); Require(fclose_nid_postfix(writer) == 0, "close write-only file"); auto* reader = fopen_nid_postfix(path, "rb"); Require(reader != nullptr, "open read-only file"); Require(putw_nid_postfix(0x12345678, reader) == -1, "write on read-only stream fails"); Require((reader->GuestState().flags & 0x60) == 0x40, "publish write error"); Require(fclose_nid_postfix(reader) == 0, "close read-only file"); FileStream closed(std::tmpfile()); Require(closed.Close() == 0, "close temporary stream"); errno = 0; Require(getw_nid_postfix(&closed) == -1 && errno == 9, "closed read reports EBADF"); errno = 0; Require(putw_nid_postfix(1, &closed) == -1 && errno == 9, "closed write reports EBADF"); bool readThrew = false, writeThrew = false; try { getw_nid_postfix(nullptr); } catch (const std::runtime_error&) { readThrew = true; } try { putw_nid_postfix(1, nullptr); } catch (const std::invalid_argument&) { writeThrew = true; } Require(readThrew && writeThrew, "null streams throw"); } void CheckConcurrentWrites(const char* path) { auto* stream = fopen_nid_postfix(path, "w+b"); Require(stream != nullptr, "open shared stream"); flockfile_nid_postfix(stream); Require(putw_nid_postfix(0, stream) == 0, "putw accepts a lock held by its caller"); funlockfile_nid_postfix(stream); const auto writer = [stream](int value) { for (int count = 0; count < 256; ++count) Require(putw_nid_postfix(value, stream) == 0, "concurrent word write"); }; std::thread first(writer, 0x11223344), second(writer, 0x55667788); first.join(); second.join(); Require(fseek_nid_postfix(stream, 4, SEEK_SET) == 0, "rewind concurrent words"); int firstCount = 0, secondCount = 0; for (int count = 0; count < 512; ++count) { const int value = getw_nid_postfix(stream); Require(value == 0x11223344 || value == 0x55667788, "concurrent words do not interleave their bytes"); if (value == 0x11223344) ++firstCount; else ++secondCount; } Require(firstCount == 256 && secondCount == 256, "both writers preserve every word"); Require(fclose_nid_postfix(stream) == 0, "close shared stream"); } } int main(int argc, char** argv) { Require(argc == 2, "expected temporary file path"); CheckRoundTrip(argv[1]); CheckShortReads(argv[1]); CheckErrors(argv[1]); CheckConcurrentWrites(argv[1]); Require(std::remove(argv[1]) == 0, "remove temporary file"); std::puts("binary word I/O checks passed"); }