123 lines
4.2 KiB
C++
123 lines
4.2 KiB
C++
#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);
|
|
}
|