feat(libc): query filesystem statistics by guest path
This commit is contained in:
parent
2a5eca11cd
commit
c052b7c720
|
|
@ -2,9 +2,19 @@ add_library(libSceLibcInternal SHARED EXCLUDE_FROM_ALL Export.cpp FileTime.cpp U
|
|||
target_include_directories(libSceLibcInternal PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(libSceLibcInternal PRIVATE ${CMAKE_DL_LIBS})
|
||||
configure_windows_unwind(libSceLibcInternal)
|
||||
target_sources(libSceLibcInternal PRIVATE FilesystemStatistics.cpp)
|
||||
target_sources(libSceLibcInternal PRIVATE FilesystemStatistics.cpp PathStatistics.cpp)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_statvfs_tests ${LIBS_INCLUDE_DIR}/tests/GuestStatvfs.cpp)
|
||||
target_include_directories(guest_statvfs_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_statvfs_tests PRIVATE libSceLibcInternal libc)
|
||||
configure_windows_unwind(guest_statvfs_tests)
|
||||
add_test(NAME guest_statvfs COMMAND guest_statvfs_tests)
|
||||
set_tests_properties(guest_statvfs PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_statvfs PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceLibcInternal>")
|
||||
endif()
|
||||
add_test_executable(guest_fstatvfs_conversion_tests ${LIBS_INCLUDE_DIR}/tests/GuestFstatvfsConversion.cpp FilesystemStatistics.cpp)
|
||||
target_include_directories(guest_fstatvfs_conversion_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
configure_windows_unwind(guest_fstatvfs_conversion_tests)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
#include "prx/libc/include/FilesystemError.hpp"
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <fcntl.h>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
struct LibcStatvfs;
|
||||
extern "C" int APS5_VABI fstatvfs_nid_postfix(int, LibcStatvfs*);
|
||||
|
||||
namespace {
|
||||
int Fail(int error) {
|
||||
errno = error;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int HostError(int error) {
|
||||
switch (error) {
|
||||
case ENOENT: return 2;
|
||||
case EINTR: return 4;
|
||||
case EIO: return 5;
|
||||
case ENOMEM: return 12;
|
||||
case EACCES: return 13;
|
||||
case ENOTDIR: return 20;
|
||||
case EINVAL: return 22;
|
||||
case ENFILE: return 23;
|
||||
case EMFILE: return 24;
|
||||
case EAGAIN: return 35;
|
||||
case ELOOP: return 62;
|
||||
case ENAMETOOLONG: return 63;
|
||||
default: throw std::system_error(error, std::generic_category(), "statvfs: unsupported host error");
|
||||
}
|
||||
}
|
||||
|
||||
struct Descriptor {
|
||||
int value;
|
||||
~Descriptor() {
|
||||
const int saved = errno;
|
||||
#ifdef _WIN32
|
||||
::_close(value);
|
||||
#else
|
||||
::close(value);
|
||||
#endif
|
||||
errno = saved;
|
||||
}
|
||||
};
|
||||
|
||||
int OpenMetadata(const std::filesystem::path& path, bool directory) {
|
||||
#ifdef _WIN32
|
||||
const HANDLE handle = CreateFileW(path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
const DWORD error = GetLastError();
|
||||
if (error == ERROR_PATH_NOT_FOUND || error == ERROR_INVALID_NAME || error == ERROR_DIRECTORY) {
|
||||
for (auto prefix = path.parent_path(); !prefix.empty();) {
|
||||
const DWORD attributes = GetFileAttributesW(prefix.c_str());
|
||||
if (attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_DIRECTORY)) return Fail(20);
|
||||
const auto parent = prefix.parent_path();
|
||||
if (parent == prefix) break;
|
||||
prefix = parent;
|
||||
}
|
||||
}
|
||||
switch (error) {
|
||||
case ERROR_FILE_NOT_FOUND:
|
||||
case ERROR_PATH_NOT_FOUND:
|
||||
case ERROR_INVALID_DRIVE: return Fail(2);
|
||||
case ERROR_ACCESS_DENIED:
|
||||
case ERROR_SHARING_VIOLATION: return Fail(13);
|
||||
case ERROR_DIRECTORY: return Fail(20);
|
||||
case ERROR_INVALID_NAME:
|
||||
case ERROR_INVALID_PARAMETER: return Fail(22);
|
||||
case ERROR_CANT_RESOLVE_FILENAME: return Fail(62);
|
||||
case ERROR_FILENAME_EXCED_RANGE: return Fail(63);
|
||||
default: throw std::system_error(error, std::system_category(), "statvfs: CreateFileW");
|
||||
}
|
||||
}
|
||||
if (directory) {
|
||||
BY_HANDLE_FILE_INFORMATION info{};
|
||||
if (!GetFileInformationByHandle(handle, &info)) {
|
||||
const DWORD error = GetLastError();
|
||||
CloseHandle(handle);
|
||||
throw std::system_error(error, std::system_category(), "statvfs: GetFileInformationByHandle");
|
||||
}
|
||||
if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
||||
CloseHandle(handle);
|
||||
return Fail(20);
|
||||
}
|
||||
}
|
||||
const int descriptor = ::_open_osfhandle(reinterpret_cast<std::intptr_t>(handle), _O_RDONLY | _O_BINARY | _O_NOINHERIT);
|
||||
if (descriptor < 0) {
|
||||
const int error = errno;
|
||||
CloseHandle(handle);
|
||||
return Fail(HostError(error));
|
||||
}
|
||||
return descriptor;
|
||||
#else
|
||||
const int descriptor = ::open(path.c_str(), O_PATH | O_CLOEXEC | (directory ? O_DIRECTORY : 0));
|
||||
return descriptor < 0 ? Fail(HostError(errno)) : descriptor;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int APS5_VABI statvfs_nid_postfix(const char* path, LibcStatvfs* output) {
|
||||
if (path == nullptr) return Fail(14);
|
||||
if (*path == '\0') return Fail(2);
|
||||
const int saved = errno;
|
||||
const auto last = std::string_view(path).back();
|
||||
const bool directory = last == '/' || last == '\\' || FinalPathComponentIsDot_nid_no_patch(path);
|
||||
std::filesystem::path native;
|
||||
if (const int error = ResolvePathError_nid_no_patch(path, native)) return Fail(error);
|
||||
const int descriptor = OpenMetadata(native, directory);
|
||||
if (descriptor < 0) return -1;
|
||||
const Descriptor owner{descriptor};
|
||||
errno = saved;
|
||||
return fstatvfs_nid_postfix(descriptor, output);
|
||||
}
|
||||
|
|
@ -223,6 +223,7 @@ static std::int64_t NativePwrite(int descriptor, const void* buf, std::size_t nb
|
|||
}
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/time.h>
|
||||
|
|
@ -387,7 +388,7 @@ static int NativeFstatfs(int descriptor, KernelStatfs* fs) {
|
|||
constexpr long PipeFilesystem = 0x50495045;
|
||||
struct stat file{};
|
||||
if (::fstat(descriptor, &file) != 0) return SceErrorFromErrno(errno) & 0xffff;
|
||||
if (S_ISSOCK(file.st_mode)) return GUEST_EINVAL;
|
||||
if (S_ISSOCK(file.st_mode) && (::fcntl(descriptor, F_GETFL) & O_PATH) == 0) return GUEST_EINVAL;
|
||||
if (S_ISFIFO(file.st_mode)) {
|
||||
struct statfs kind{};
|
||||
if (::fstatfs(descriptor, &kind) == 0 && static_cast<long>(kind.f_type) == PipeFilesystem) return GUEST_EINVAL;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,157 @@
|
|||
#include "prx/libc/include/General.hpp"
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
#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<std::uint64_t, 13> 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<std::uint64_t, 11> 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::size_t>(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<bool>(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<bool>(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<const sockaddr*>(&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<char>(stream)), std::istreambuf_iterator<char>());
|
||||
Require(text == "unchanged");
|
||||
}
|
||||
RemovePathAlias_nid_no_patch("/anyps5-statvfs-test");
|
||||
Require(std::filesystem::remove(file));
|
||||
Require(std::filesystem::remove(root));
|
||||
}
|
||||
Loading…
Reference in New Issue