mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 08:33:12 -05:00
142 lines
6.9 KiB
C++
142 lines
6.9 KiB
C++
#include "prx/libc/include/FileStream.hpp"
|
|
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include <array>
|
|
#include <cerrno>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
#include <thread>
|
|
|
|
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<int>::min(), std::numeric_limits<int>::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<std::int64_t>(4 * values.size()), "write exactly four bytes per word");
|
|
Require(fseek_nid_postfix(stream, 0, SEEK_SET) == 0, "rewind written words");
|
|
std::array<unsigned char, 4 * values.size()> 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<std::uint32_t>(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<unsigned char, 3> 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<std::int64_t>(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");
|
|
}
|