#include "prx/libc/include/General.hpp" #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #else #include #include #include #include #endif extern "C" int APS5_VABI statvfs_nid_postfix(const char*, void*); extern "C" int* APS5_VABI __error_nid_postfix(); static void Check(bool value, int line) { if (!value) { std::fprintf(stderr, "statvfs check failed at line %d\n", line); std::abort(); } } #define Require(value) Check((value), __LINE__) static void RequireVolume(const char* path, const std::filesystem::path& native) { std::array output; output.fill(0xA5A5A5A5A5A5A5A5ull); *__error_nid_postfix() = 12345; Require(statvfs_nid_postfix(path, output.data() + 1) == 0); Require(*__error_nid_postfix() == 12345); Require(output.front() == 0xA5A5A5A5A5A5A5A5ull && output.back() == output.front()); const auto* fields = output.data() + 1; Require(fields[6] > 0 && fields[8] > 0); Require(fields[2] == std::filesystem::space(native).capacity / fields[8]); Require(fields[0] <= fields[1] && fields[1] <= fields[2]); Require((fields[7] & ~std::uint64_t{3}) == 0); Require(fields[9] == 0 && fields[10] >= 255); } static void RequireError(const char* path, int error) { std::array output; output.fill(0x5A5A5A5A5A5A5A5Aull); const auto saved = output; *__error_nid_postfix() = 0; const int result = statvfs_nid_postfix(path, output.data()); if (result != -1) std::fprintf(stderr, "path %s: expected failure, got %d\n", path ? path : "(null)", result); Require(result == -1); if (*__error_nid_postfix() != error) std::fprintf(stderr, "path %s: expected errno %d, got %d\n", path ? path : "(null)", error, *__error_nid_postfix()); Require(*__error_nid_postfix() == error && output == saved); } static std::size_t OpenHandles() { #ifdef _WIN32 DWORD count = 0; Require(GetProcessHandleCount(GetCurrentProcess(), &count) != 0); return count; #else return static_cast(std::distance(std::filesystem::directory_iterator("/proc/self/fd"), std::filesystem::directory_iterator{})); #endif } int main() { const auto root = std::filesystem::temp_directory_path() / ("anyps5-statvfs-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); Require(std::filesystem::create_directory(root)); const auto file = root / "file.txt"; { std::ofstream stream(file, std::ios::binary); stream << "unchanged"; Require(static_cast(stream)); } AddPathAlias_nid_no_patch("/anyps5-statvfs-test", root.string().c_str()); RequireVolume("/anyps5-statvfs-test", root); RequireVolume("/anyps5-statvfs-test/file.txt", file); RequireVolume("/anyps5-statvfs-test/", root); RequireError(nullptr, 14); RequireError("", 2); RequireError("/anyps5-statvfs-test/missing", 2); RequireError("/anyps5-statvfs-test/file.txt/child", 20); RequireError("/anyps5-statvfs-test/file.txt/", 20); RequireError("/anyps5-statvfs-test/file.txt/.", 20); RequireError("/anyps5-statvfs-test/file.txt/../file.txt", 20); RequireError("/anyps5-statvfs-test/missing/../file.txt", 2); BlockPathAlias_nid_no_patch("/anyps5-statvfs-unavailable"); RequireError("/anyps5-statvfs-unavailable", 2); AddPathAlias_nid_no_patch("/anyps5-statvfs-unavailable", root.string().c_str()); RemovePathAlias_nid_no_patch("/anyps5-statvfs-unavailable"); Require(statvfs_nid_postfix("/anyps5-statvfs-test/file.txt", nullptr) == -1); Require(*__error_nid_postfix() == 14); const auto unicodeFile = root / std::filesystem::path(u8"\u65e5.txt"); { std::ofstream stream(unicodeFile); stream << "unicode path"; Require(static_cast(stream)); } RequireVolume("/anyps5-statvfs-test/\xE6\x97\xA5.txt", unicodeFile); Require(std::filesystem::remove(unicodeFile)); #ifdef _WIN32 const HANDLE exclusive = CreateFileW(file.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); Require(exclusive != INVALID_HANDLE_VALUE); RequireVolume("/anyps5-statvfs-test/file.txt", file); Require(CloseHandle(exclusive) != 0); #else Require(::chmod(file.c_str(), 0) == 0); RequireVolume("/anyps5-statvfs-test/file.txt", file); Require(::chmod(file.c_str(), 0600) == 0); const auto fifo = root / "fifo"; Require(::mkfifo(fifo.c_str(), 0600) == 0); RequireVolume("/anyps5-statvfs-test/fifo", fifo); const auto socketPath = root / "socket"; const int socket = ::socket(AF_UNIX, SOCK_STREAM, 0); Require(socket >= 0); sockaddr_un address{}; address.sun_family = AF_UNIX; const auto name = socketPath.string(); Require(name.size() < sizeof(address.sun_path)); std::memcpy(address.sun_path, name.c_str(), name.size() + 1); Require(::bind(socket, reinterpret_cast(&address), sizeof(address)) == 0); RequireVolume("/anyps5-statvfs-test/socket", socketPath); Require(::close(socket) == 0); Require(std::filesystem::remove(socketPath)); Require(std::filesystem::remove(fifo)); std::filesystem::create_symlink(file.filename(), root / "link"); RequireVolume("/anyps5-statvfs-test/link", file); Require(std::filesystem::remove(root / "link")); std::filesystem::create_symlink("missing", root / "dangling"); RequireError("/anyps5-statvfs-test/dangling", 2); Require(std::filesystem::remove(root / "dangling")); #endif const auto before = OpenHandles(); for (int i = 0; i != 32; ++i) { RequireVolume("/anyps5-statvfs-test/file.txt", file); Require(statvfs_nid_postfix("/anyps5-statvfs-test/file.txt", nullptr) == -1); Require(*__error_nid_postfix() == 14); } Require(OpenHandles() == before); { std::ifstream stream(file, std::ios::binary); std::string text((std::istreambuf_iterator(stream)), std::istreambuf_iterator()); Require(text == "unchanged"); } RemovePathAlias_nid_no_patch("/anyps5-statvfs-test"); Require(std::filesystem::remove(file)); Require(std::filesystem::remove(root)); }