mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-21 08:12:09 -05:00
233 lines
12 KiB
C++
233 lines
12 KiB
C++
#include "prx/libc/include/FileStream.hpp"
|
|
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include <array>
|
|
#include <barrier>
|
|
#include <cerrno>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <fcntl.h>
|
|
#ifdef _WIN32
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
#include <io.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
extern "C" {
|
|
FileStream* APS5_VABI fopen_nid_postfix(const char* filename, const char* mode);
|
|
FileStream* APS5_VABI _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(const char*, int, int);
|
|
FileStream* APS5_VABI freopen_nid_postfix(const char* filename, const char* mode, FileStream* stream);
|
|
int APS5_VABI fclose_nid_postfix(FileStream* stream);
|
|
std::size_t APS5_VABI fread_nid_postfix(void* buffer, std::size_t size, std::size_t count, FileStream* stream);
|
|
std::size_t APS5_VABI fwrite_nid_postfix(const void* buffer, std::size_t size, std::size_t count, FileStream* stream);
|
|
int APS5_VABI fseek_nid_postfix(FileStream* stream, std::int64_t offset, int origin);
|
|
std::int64_t APS5_VABI ftell_nid_postfix(FileStream* stream);
|
|
int APS5_VABI fputs_nid_postfix(const char* str, FileStream* stream);
|
|
int APS5_VABI fflush_nid_postfix(FileStream* stream);
|
|
int APS5_VABI remove_nid_postfix(const char* path);
|
|
int APS5_VABI rename_nid_postfix(const char* from, const char* to);
|
|
int APS5_VABI ferror_nid_postfix(FileStream* stream);
|
|
void APS5_VABI clearerr_nid_postfix(FileStream* stream);
|
|
}
|
|
|
|
static void Require(bool condition, const char* message = "File stream check failed") {
|
|
if (!condition) throw std::runtime_error(message);
|
|
}
|
|
|
|
template<typename TAction>
|
|
static void ExpectException(TAction action) {
|
|
try {
|
|
action();
|
|
} catch (const std::runtime_error&) {
|
|
return;
|
|
}
|
|
throw std::runtime_error("Expected a stream exception");
|
|
}
|
|
|
|
static void ReplaceWithReadOnlyDescriptor(FileStream* stream, const char* path) {
|
|
#ifdef _WIN32
|
|
const int descriptor = _open(path, _O_RDONLY | _O_BINARY);
|
|
Require(descriptor >= 0 && _dup2(descriptor, _fileno(GetNativeStream(stream))) == 0);
|
|
Require(_close(descriptor) == 0);
|
|
#else
|
|
const int descriptor = open(path, O_RDONLY);
|
|
Require(descriptor >= 0 && dup2(descriptor, fileno(GetNativeStream(stream))) >= 0);
|
|
Require(close(descriptor) == 0);
|
|
#endif
|
|
}
|
|
|
|
static void CheckExclusiveCppOpen(const std::string& base) {
|
|
constexpr int Out = 0x02, Binary = 0x80, Noreplace = 0x40;
|
|
const std::string path = base + ".exclusive";
|
|
std::remove(path.c_str());
|
|
auto* stream = _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(path.c_str(), Out | Binary | Noreplace, 0);
|
|
Require(stream != nullptr);
|
|
constexpr char original[] = "keep existing contents";
|
|
Require(fwrite_nid_postfix(original, 1, sizeof(original), stream) == sizeof(original));
|
|
Require(fclose_nid_postfix(stream) == 0);
|
|
#ifdef _WIN32
|
|
const auto nativePath = std::filesystem::path(path).wstring();
|
|
const auto writer = CreateFileW(nativePath.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
|
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
Require(writer != INVALID_HANDLE_VALUE);
|
|
auto* refused = _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(path.c_str(), Out | Binary | Noreplace, 0);
|
|
if (refused) fclose_nid_postfix(refused);
|
|
Require(CloseHandle(writer) != 0);
|
|
Require(refused == nullptr, "C++ noreplace truncated an existing write-shared file");
|
|
#endif
|
|
stream = fopen_nid_postfix(path.c_str(), "rb");
|
|
Require(stream != nullptr);
|
|
std::array<char, sizeof(original)> saved{};
|
|
Require(fread_nid_postfix(saved.data(), 1, saved.size(), stream) == saved.size());
|
|
Require(std::memcmp(saved.data(), original, saved.size()) == 0);
|
|
Require(fclose_nid_postfix(stream) == 0 && std::remove(path.c_str()) == 0);
|
|
for (const int mode : {0x42, 0x52, 0x53, 0xc2, 0xd2, 0xd3}) {
|
|
stream = _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(path.c_str(), mode | 0x04, 0);
|
|
Require(stream != nullptr && ftell_nid_postfix(stream) == 0);
|
|
Require(fwrite_nid_postfix(original, 1, sizeof(original), stream) == sizeof(original));
|
|
Require(fclose_nid_postfix(stream) == 0);
|
|
errno = 0;
|
|
Require(_ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(path.c_str(), mode, 0) == nullptr && errno == EEXIST);
|
|
stream = fopen_nid_postfix(path.c_str(), "rb");
|
|
Require(stream != nullptr && fread_nid_postfix(saved.data(), 1, saved.size(), stream) == saved.size());
|
|
Require(std::memcmp(saved.data(), original, saved.size()) == 0);
|
|
Require(fclose_nid_postfix(stream) == 0 && std::remove(path.c_str()) == 0);
|
|
}
|
|
for (int round = 0; round < 12; ++round) {
|
|
std::array<FileStream*, 2> created{};
|
|
std::barrier start(2);
|
|
const auto create = [&](int index) {
|
|
start.arrive_and_wait();
|
|
created[index] = _ZSt7_FiopenPKcNSt5_IosbIiE9_OpenmodeEi_nid_postfix(path.c_str(), Out | Binary | Noreplace, 0);
|
|
};
|
|
std::thread first(create, 0), second(create, 1);
|
|
first.join();
|
|
second.join();
|
|
const auto winners = static_cast<int>(created[0] != nullptr) + static_cast<int>(created[1] != nullptr);
|
|
for (auto* opened : created) if (opened) Require(fclose_nid_postfix(opened) == 0);
|
|
Require(winners == 1 && std::remove(path.c_str()) == 0);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
Require(argc == 2);
|
|
CheckExclusiveCppOpen(argv[1]);
|
|
Require(_Stdout_nid_postfix.GetHandle() == stdout);
|
|
Require(_Stderr_nid_postfix.GetHandle() == stderr);
|
|
Require(fputs_nid_postfix("stdout object works\n", &_Stdout_nid_postfix) >= 0);
|
|
Require(fputs_nid_postfix("stderr object works\n", &_Stderr_nid_postfix) >= 0);
|
|
Require(fflush_nid_postfix(nullptr) == 0);
|
|
auto* stream = fopen_nid_postfix(argv[1], "w+b");
|
|
constexpr char payload[] = "stream round trip";
|
|
Require(fputs_nid_postfix(payload, stream) >= 0);
|
|
Require(fwrite_nid_postfix(payload, 1, sizeof(payload), stream) == sizeof(payload));
|
|
const auto expectedSize = sizeof(payload) * 2 - 1;
|
|
Require(ftell_nid_postfix(stream) == static_cast<long>(expectedSize));
|
|
Require(fflush_nid_postfix(stream) == 0);
|
|
Require(fseek_nid_postfix(stream, 0, SEEK_SET) == 0);
|
|
std::array<char, sizeof(payload)> buffer{};
|
|
Require(fread_nid_postfix(buffer.data(), 1, sizeof(payload) - 1, stream) == sizeof(payload) - 1);
|
|
Require(std::strcmp(buffer.data(), payload) == 0);
|
|
Require(fread_nid_postfix(buffer.data(), 1, buffer.size(), stream) == buffer.size());
|
|
Require(std::strcmp(buffer.data(), payload) == 0);
|
|
Require(fread_nid_postfix(buffer.data(), 1, buffer.size(), stream) == 0);
|
|
ExpectException([&] { fputs_nid_postfix(nullptr, stream); });
|
|
ExpectException([&] { fwrite_nid_postfix(nullptr, 1, sizeof(payload), stream); });
|
|
errno = 0;
|
|
Require(fseek_nid_postfix(stream, 0, -1) == -1);
|
|
Require(errno == EINVAL);
|
|
Require(fclose_nid_postfix(stream) == 0);
|
|
ExpectException([] { fputs_nid_postfix("invalid stream", nullptr); });
|
|
ExpectException([] { fopen_nid_postfix(nullptr, "r"); });
|
|
FileStream closed(std::tmpfile());
|
|
closed.Close();
|
|
Require(fflush_nid_postfix(&closed) == -1 && errno == 9);
|
|
Require(std::remove(argv[1]) == 0);
|
|
errno = 0;
|
|
Require(fopen_nid_postfix(argv[1], "rb") == nullptr);
|
|
Require(errno == ENOENT);
|
|
#ifdef _WIN32
|
|
const std::string directoryMode = "rb";
|
|
constexpr int directoryError = EACCES;
|
|
#else
|
|
const std::string directoryMode = "wb";
|
|
constexpr int directoryError = EISDIR;
|
|
#endif
|
|
const std::string directoryName = std::string(argv[1]) + ".dir";
|
|
Require(std::filesystem::create_directory(directoryName));
|
|
errno = 0;
|
|
Require(fopen_nid_postfix(directoryName.c_str(), directoryMode.c_str()) == nullptr);
|
|
Require(errno == directoryError);
|
|
Require(std::filesystem::remove(directoryName));
|
|
const std::u8string unicodeName = u8"セーブé.tmp";
|
|
const std::string guestName(unicodeName.begin(), unicodeName.end());
|
|
const std::filesystem::path hostName(unicodeName);
|
|
constexpr char unicodePayload[] = "unicode";
|
|
stream = fopen_nid_postfix(guestName.c_str(), "wb");
|
|
Require(stream != nullptr);
|
|
Require(fwrite_nid_postfix(unicodePayload, 1, sizeof(unicodePayload), stream) == sizeof(unicodePayload));
|
|
Require(fclose_nid_postfix(stream) == 0);
|
|
Require(std::filesystem::exists(hostName));
|
|
Require(std::filesystem::file_size(hostName) == sizeof(unicodePayload));
|
|
stream = fopen_nid_postfix(guestName.c_str(), "rb");
|
|
Require(stream != nullptr);
|
|
std::array<char, sizeof(unicodePayload)> unicodeBuffer{};
|
|
Require(fread_nid_postfix(unicodeBuffer.data(), 1, unicodeBuffer.size(), stream) == unicodeBuffer.size());
|
|
Require(std::strcmp(unicodeBuffer.data(), unicodePayload) == 0);
|
|
stream = freopen_nid_postfix(guestName.c_str(), "wb", stream);
|
|
Require(stream != nullptr);
|
|
Require(fclose_nid_postfix(stream) == 0);
|
|
Require(std::filesystem::file_size(hostName) == 0);
|
|
std::filesystem::remove(hostName);
|
|
const std::string heldName = std::string(argv[1]) + ".held";
|
|
const std::string movedName = std::string(argv[1]) + ".moved";
|
|
stream = fopen_nid_postfix(heldName.c_str(), "w+b");
|
|
Require(stream != nullptr && fwrite_nid_postfix(payload, 1, sizeof(payload), stream) == sizeof(payload));
|
|
Require(fflush_nid_postfix(stream) == 0);
|
|
Require(rename_nid_postfix(heldName.c_str(), movedName.c_str()) == 0 && !std::filesystem::exists(heldName));
|
|
Require(remove_nid_postfix(movedName.c_str()) == 0 && !std::filesystem::exists(movedName));
|
|
Require(fseek_nid_postfix(stream, 0, SEEK_SET) == 0);
|
|
std::array<char, sizeof(payload)> heldBuffer{};
|
|
Require(fread_nid_postfix(heldBuffer.data(), 1, heldBuffer.size(), stream) == heldBuffer.size());
|
|
Require(std::strcmp(heldBuffer.data(), payload) == 0 && fclose_nid_postfix(stream) == 0);
|
|
stream = fopen_nid_postfix(heldName.c_str(), "ab");
|
|
Require(stream != nullptr && fputs_nid_postfix("ab", stream) >= 0 && fclose_nid_postfix(stream) == 0);
|
|
stream = fopen_nid_postfix(heldName.c_str(), "ab");
|
|
Require(stream != nullptr && fputs_nid_postfix("cd", stream) >= 0 && fclose_nid_postfix(stream) == 0);
|
|
errno = 0;
|
|
Require(fopen_nid_postfix(heldName.c_str(), "wbx") == nullptr && errno == EEXIST);
|
|
stream = fopen_nid_postfix(heldName.c_str(), "rb");
|
|
std::array<char, 5> appended{};
|
|
Require(stream != nullptr && fread_nid_postfix(appended.data(), 1, 4, stream) == 4 && std::strcmp(appended.data(), "abcd") == 0);
|
|
Require(fclose_nid_postfix(stream) == 0 && remove_nid_postfix(heldName.c_str()) == 0);
|
|
constexpr char marker[] = "marker";
|
|
stream = fopen_nid_postfix(argv[1], "wb");
|
|
Require(stream != nullptr);
|
|
std::array<char, sizeof(marker)> markerBuffer{};
|
|
Require(fread_nid_postfix(markerBuffer.data(), 1, markerBuffer.size(), stream) == 0);
|
|
Require(ferror_nid_postfix(stream) != 0);
|
|
Require(fclose_nid_postfix(stream) == 0);
|
|
stream = fopen_nid_postfix(argv[1], "rb");
|
|
Require(stream != nullptr);
|
|
Require(fwrite_nid_postfix(marker, 1, sizeof(marker), stream) == 0);
|
|
Require(ferror_nid_postfix(stream) != 0);
|
|
clearerr_nid_postfix(stream);
|
|
Require(fputs_nid_postfix(marker, stream) == EOF);
|
|
Require(ferror_nid_postfix(stream) != 0);
|
|
Require(fclose_nid_postfix(stream) == 0);
|
|
stream = fopen_nid_postfix(argv[1], "wb");
|
|
Require(stream != nullptr && fputs_nid_postfix(marker, stream) >= 0);
|
|
ReplaceWithReadOnlyDescriptor(stream, argv[1]);
|
|
Require(fflush_nid_postfix(stream) == EOF);
|
|
Require(ferror_nid_postfix(stream) != 0);
|
|
static_cast<void>(fclose_nid_postfix(stream));
|
|
Require(std::remove(argv[1]) == 0);
|
|
std::cout << "PASS: stream objects, file operations, EOF and error handling\n";
|
|
}
|