AnyPS5/core/libs/tests/GuestFileLock.cpp

307 lines
12 KiB
C++

#include "prx/libc/include/general/VabiMacros.hpp"
#include <barrier>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <future>
#include <set>
#include <string>
#include <thread>
#include <windows.h>
class FileStream;
extern "C" {
int APS5_VABI open_nid_postfix(const char*, int, int);
int APS5_VABI close_nid_postfix(int);
int APS5_VABI sceKernelClose(int);
int APS5_VABI dup_nid_postfix(int);
int APS5_VABI dup2_nid_postfix(int, int);
int APS5_VABI fcntl_nid_postfix(int, int, ...);
int APS5_VABI flock_nid_postfix(int, int);
int* APS5_VABI __error_nid_postfix();
FileStream* APS5_VABI fopen_nid_postfix(const char*, const char*);
FileStream* APS5_VABI fdopen_nid_postfix(int, const char*);
FileStream* APS5_VABI freopen_nid_postfix(const char*, const char*, FileStream*);
int APS5_VABI fclose_nid_postfix(FileStream*);
int APS5_VABI fileno_nid_postfix(FileStream*);
}
static void Require(bool condition) {
if (!condition) std::abort();
}
static int Duplicate(int source, int kind) {
if (kind == 0) return dup_nid_postfix(source);
if (kind == 1) return dup2_nid_postfix(source, 20);
return fcntl_nid_postfix(source, 0, 20);
}
static bool Acquires(int descriptor, int mode) {
const int result = flock_nid_postfix(descriptor, mode | 4);
if (result == 0) {
Require(flock_nid_postfix(descriptor, 8) == 0);
return true;
}
Require(result == -1 && *__error_nid_postfix() == 35);
return false;
}
struct Fixture {
static inline unsigned serial = 0;
std::string path = (std::filesystem::temp_directory_path() / ("anyps5-flock-" + std::to_string(::GetCurrentProcessId()) + "-" + std::to_string(serial++) + ".bin")).string();
std::set<int> descriptors;
int source = Open(true);
int other = Open(false);
int Open(bool create) {
const int descriptor = open_nid_postfix(path.c_str(), create ? 0x602 : 2, 0644);
Require(descriptor >= 0);
descriptors.insert(descriptor);
return descriptor;
}
int Copy(int kind, int from = -1) {
const int descriptor = Duplicate(from < 0 ? source : from, kind);
Require(descriptor >= 0);
descriptors.insert(descriptor);
return descriptor;
}
void Close(int descriptor, bool kernel = false) {
Require((kernel ? sceKernelClose(descriptor) : close_nid_postfix(descriptor)) == 0);
descriptors.erase(descriptor);
}
~Fixture() {
for (const int descriptor : descriptors) Require(close_nid_postfix(descriptor) == 0);
Require(std::remove(path.c_str()) == 0);
}
};
static void Duplicates() {
for (int kind = 0; kind != 3; ++kind) {
for (bool before : {false, true}) {
for (int mode : {1, 2}) {
Fixture fixture;
int copy = before ? fixture.Copy(kind) : -1;
Require(flock_nid_postfix(fixture.source, mode | 4) == 0);
if (!before) copy = fixture.Copy(kind);
Require(flock_nid_postfix(copy, mode | 4) == 0);
Require(flock_nid_postfix(fixture.source, mode | 4) == 0);
Require(!Acquires(fixture.other, 2));
Require(flock_nid_postfix(copy, 8) == 0);
Require(Acquires(fixture.other, 2));
Require(flock_nid_postfix(fixture.source, 8) == 0);
}
}
}
}
static void Lifetime() {
for (int kind = 0; kind != 3; ++kind) {
for (bool kernel : {false, true}) {
Fixture fixture;
const int copy = fixture.Copy(kind);
Require(flock_nid_postfix(fixture.source, 2 | 4) == 0);
const int chain = fixture.Copy(0, copy);
fixture.Close(fixture.source, kernel);
fixture.Close(copy, !kernel);
Require(!Acquires(fixture.other, 2));
Require(flock_nid_postfix(chain, 8) == 0);
Require(Acquires(fixture.other, 2));
Require(flock_nid_postfix(chain, 2 | 4) == 0);
fixture.Close(chain, kernel);
Require(Acquires(fixture.other, 2));
}
}
{
Fixture fixture;
const int copy = fixture.Copy(0);
Require(flock_nid_postfix(fixture.source, 2 | 4) == 0);
Require(dup2_nid_postfix(copy, fixture.source) == fixture.source);
Require(dup2_nid_postfix(fixture.source, fixture.source) == fixture.source);
Require(!Acquires(fixture.other, 2));
Require(flock_nid_postfix(fixture.source, 8) == 0);
Require(Acquires(fixture.other, 2));
}
{
Fixture first;
Fixture second;
const int copy = second.Copy(0);
Require(flock_nid_postfix(second.source, 2 | 4) == 0);
Require(dup2_nid_postfix(first.source, second.source) == second.source);
Require(!Acquires(second.other, 2));
Require(flock_nid_postfix(copy, 8) == 0);
Require(Acquires(second.other, 2));
Require(flock_nid_postfix(second.source, 2 | 4) == 0);
Require(!Acquires(first.other, 2));
Require(flock_nid_postfix(first.source, 8) == 0);
Require(Acquires(first.other, 2));
}
{
Fixture fixture;
const int previous = fixture.source;
Require(flock_nid_postfix(previous, 2 | 4) == 0);
fixture.Close(previous);
const int recycled = fixture.Open(false);
Require(recycled == previous && Acquires(recycled, 2));
Require(Acquires(fixture.other, 2));
}
}
static void Conversions() {
for (int kind = 0; kind != 3; ++kind) {
Fixture fixture;
const int copy = fixture.Copy(kind);
for (int repeat = 0; repeat != 20; ++repeat) {
Require(flock_nid_postfix(fixture.source, 2 | 4) == 0);
Require(flock_nid_postfix(copy, 1 | 4) == 0);
Require(Acquires(fixture.other, 1) && !Acquires(fixture.other, 2));
Require(flock_nid_postfix(fixture.source, 2 | 4) == 0);
Require(!Acquires(fixture.other, 1));
Require(flock_nid_postfix(copy, 8) == 0 && Acquires(fixture.other, 2));
}
Require(flock_nid_postfix(fixture.source, 1 | 4) == 0);
Require(flock_nid_postfix(fixture.other, 1 | 4) == 0);
Require(flock_nid_postfix(copy, 2 | 4) == -1 && *__error_nid_postfix() == 35);
Require(flock_nid_postfix(fixture.other, 8) == 0);
Require(Acquires(fixture.other, 2));
}
}
static void Threads() {
for (int kind = 0; kind != 3; ++kind) {
Fixture fixture;
Require(flock_nid_postfix(fixture.source, 2 | 4) == 0);
const int copy = fixture.Copy(kind);
auto repeated = std::async(std::launch::async, [&] { return flock_nid_postfix(copy, 2); });
Require(repeated.wait_for(std::chrono::seconds(2)) == std::future_status::ready);
Require(repeated.get() == 0 && flock_nid_postfix(copy, 8) == 0);
}
for (bool duplicate : {false, true}) {
Fixture fixture;
const int copy = duplicate ? fixture.Copy(0) : fixture.source;
std::barrier gate(3);
int results[2] = {-2, -2};
std::thread first([&] { gate.arrive_and_wait(); results[0] = flock_nid_postfix(fixture.source, 1 | 4); });
std::thread second([&] { gate.arrive_and_wait(); results[1] = flock_nid_postfix(copy, 1 | 4); });
gate.arrive_and_wait();
first.join();
second.join();
Require(results[0] == 0 && results[1] == 0);
Require(flock_nid_postfix(copy, 8) == 0 && Acquires(fixture.other, 2));
}
Fixture first;
Fixture unrelated;
Require(flock_nid_postfix(first.source, 2 | 4) == 0);
std::promise<void> entered;
auto entry = entered.get_future();
auto waiting = std::async(std::launch::async, [&] {
entered.set_value();
return flock_nid_postfix(first.other, 2);
});
entry.wait();
Require(waiting.wait_for(std::chrono::milliseconds(200)) == std::future_status::timeout);
Require(Acquires(unrelated.source, 2));
Require(flock_nid_postfix(first.source, 8) == 0);
Require(waiting.wait_for(std::chrono::seconds(2)) == std::future_status::ready && waiting.get() == 0);
Require(flock_nid_postfix(first.other, 8) == 0);
}
static void Streams() {
Fixture first;
Fixture second;
auto* stream = fopen_nid_postfix(first.path.c_str(), "r+b");
Require(stream != nullptr);
const int descriptor = fileno_nid_postfix(stream);
Require(flock_nid_postfix(descriptor, 2 | 4) == 0);
Require(fclose_nid_postfix(stream) == 0 && Acquires(first.other, 2));
for (const auto* fixture : {&second, &first}) {
stream = fopen_nid_postfix(fixture->path.c_str(), "r+b");
Require(stream != nullptr && fileno_nid_postfix(stream) == descriptor);
Require(flock_nid_postfix(descriptor, 2 | 4) == 0 && !Acquires(fixture->other, 2));
Require(fclose_nid_postfix(stream) == 0);
}
const int recycled = first.Open(false);
Require(recycled == descriptor && flock_nid_postfix(recycled, 2 | 4) == 0);
Require(!Acquires(first.other, 2));
const int copy = first.Copy(0, recycled);
stream = fdopen_nid_postfix(recycled, "r+");
Require(stream != nullptr && !Acquires(first.other, 2));
Require(fclose_nid_postfix(stream) == 0);
first.descriptors.erase(recycled);
Require(!Acquires(first.other, 2));
Require(flock_nid_postfix(copy, 8) == 0 && Acquires(first.other, 2));
stream = fopen_nid_postfix(first.path.c_str(), "r+b");
Require(stream != nullptr);
const int reopened = fileno_nid_postfix(stream);
Require(flock_nid_postfix(reopened, 2 | 4) == 0);
const int oldCopy = first.Copy(0, reopened);
Require(freopen_nid_postfix(second.path.c_str(), "r+b", stream) == stream);
Require(!Acquires(first.other, 2));
Require(flock_nid_postfix(oldCopy, 8) == 0 && Acquires(first.other, 2));
Require(flock_nid_postfix(fileno_nid_postfix(stream), 2 | 4) == 0 && !Acquires(second.other, 2));
const auto missing = first.path + ".missing";
Require(freopen_nid_postfix(missing.c_str(), "r+b", stream) == nullptr);
Require(Acquires(second.other, 2));
stream = fopen_nid_postfix(second.path.c_str(), "r+b");
Require(stream != nullptr);
Require(flock_nid_postfix(fileno_nid_postfix(stream), 2 | 4) == 0 && !Acquires(second.other, 2));
Require(fclose_nid_postfix(stream) == 0);
}
static int ChildTry(const std::string& path, int mode) {
wchar_t executable[32768];
Require(::GetModuleFileNameW(nullptr, executable, 32768) != 0);
std::wstring command = L"\"" + std::wstring(executable) + L"\" child \"" + std::filesystem::path(path).wstring() + L"\" " + std::to_wstring(mode | 4);
STARTUPINFOW startup{};
startup.cb = sizeof(startup);
PROCESS_INFORMATION process{};
Require(::CreateProcessW(nullptr, command.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &startup, &process) != 0);
::CloseHandle(process.hThread);
if (::WaitForSingleObject(process.hProcess, 3000) != WAIT_OBJECT_0) {
::TerminateProcess(process.hProcess, 99);
std::abort();
}
DWORD result = 99;
Require(::GetExitCodeProcess(process.hProcess, &result) != 0);
::CloseHandle(process.hProcess);
return static_cast<int>(result);
}
static void Processes() {
for (int kind = 0; kind != 3; ++kind) {
for (int mode : {1, 2}) {
Fixture fixture;
Require(flock_nid_postfix(fixture.source, mode | 4) == 0);
const int copy = fixture.Copy(kind);
Require(ChildTry(fixture.path, 2) == 35);
Require(ChildTry(fixture.path, 1) == (mode == 1 ? 0 : 35));
fixture.Close(fixture.source);
Require(ChildTry(fixture.path, 2) == 35);
Require(flock_nid_postfix(copy, 8) == 0);
Require(ChildTry(fixture.path, 2) == 0);
}
}
}
int main(int argc, char** argv) {
if (argc == 4 && std::string(argv[1]) == "child") {
const int descriptor = open_nid_postfix(argv[2], 2, 0);
Require(descriptor >= 0);
const int result = flock_nid_postfix(descriptor, std::atoi(argv[3]));
const int error = result == 0 ? 0 : *__error_nid_postfix();
if (result == 0) Require(flock_nid_postfix(descriptor, 8) == 0);
Require(close_nid_postfix(descriptor) == 0);
return error;
}
Require(argc == 2);
const std::string mode = argv[1];
if (mode == "duplicate") Duplicates();
else if (mode == "lifetime") Lifetime();
else if (mode == "conversion") Conversions();
else if (mode == "threads") Threads();
else if (mode == "streams") Streams();
else if (mode == "process") Processes();
else std::abort();
}