Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion core/libs/prx/libc/src/General.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,46 @@ struct NameMatch {
bool ambiguous = false;
};

#ifdef _WIN32
bool CaseInsensitiveDirectoryWindows(const std::filesystem::path& directory) {
const auto handle = CreateFileW(directory.c_str(), FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
if (handle == INVALID_HANDLE_VALUE) return false;
const std::unique_ptr<void, decltype(&CloseHandle)> owner(handle, &CloseHandle);
struct { ULONG flags; } information{};
constexpr auto caseSensitiveInfo = static_cast<FILE_INFO_BY_HANDLE_CLASS>(23);
return GetFileInformationByHandleEx(handle, caseSensitiveInfo, &information, sizeof(information)) &&
(information.flags & FILE_CS_FLAG_CASE_SENSITIVE_DIR) == 0;
}

std::error_code MatchNamesWindows(const std::filesystem::path& directory, std::span<NameMatch> names) {
for (auto& name : names) {
const auto pattern = directory / name.name;
WIN32_FIND_DATAW data{};
const auto handle = FindFirstFileExW(pattern.c_str(), FindExInfoBasic, &data, FindExSearchNameMatch, nullptr, 0);
if (handle == INVALID_HANDLE_VALUE) {
const auto error = GetLastError();
if (error == ERROR_FILE_NOT_FOUND || error == ERROR_NO_MORE_FILES) continue;
return {static_cast<int>(error), std::system_category()};
}
const std::unique_ptr<void, decltype(&FindClose)> owner(handle, &FindClose);
do {
const auto filename = std::filesystem::path(data.cFileName);
if (!SameName(filename.string(), name.name)) continue;
name.ambiguous = name.ambiguous || name.path.has_value();
name.path = directory / filename;
} while (FindNextFileW(handle, &data));
const auto error = GetLastError();
if (error != ERROR_NO_MORE_FILES) return {static_cast<int>(error), std::system_category()};
}
return {};
}
#endif

std::error_code MatchNames(const std::filesystem::path& directory, std::span<NameMatch> names) {
#ifdef _WIN32
if (CaseInsensitiveDirectoryWindows(directory)) return MatchNamesWindows(directory, names);
#endif
std::error_code error;
std::filesystem::directory_iterator entry(directory, error), end;
for (; !error && entry != end; entry.increment(error)) {
Expand Down Expand Up @@ -141,8 +180,26 @@ std::filesystem::path Child(std::filesystem::path parent, const std::string& nam
std::filesystem::path ResolveHostPath(std::filesystem::path root, const std::vector<std::string>& relative, bool module = false) {
const auto direct = Child(root, JoinNames(relative));
std::error_code error;
auto first = relative.begin();
#ifdef _WIN32
const auto attributes = GetFileAttributesW(direct.c_str());
if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) return direct;
const auto failure = GetLastError();
const bool missing = attributes == INVALID_FILE_ATTRIBUTES && (failure == ERROR_FILE_NOT_FOUND || failure == ERROR_PATH_NOT_FOUND);
if (!missing && std::filesystem::exists(direct, error)) return direct;
for (auto last = relative.end(); last != relative.begin();) {
const auto parent = Child(root, JoinNames(std::vector<std::string>(relative.begin(), std::prev(last))));
const auto parentAttributes = GetFileAttributesW(parent.c_str());
const bool present = parentAttributes != INVALID_FILE_ATTRIBUTES && (parentAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0;
if (!present && !std::filesystem::exists(parent, error)) { --last; continue; }
root = parent;
first = std::prev(last);
break;
}
#else
if (std::filesystem::exists(direct, error)) return direct;
for (auto part = relative.begin(); part != relative.end(); ++part) {
#endif
for (auto part = first; part != relative.end(); ++part) {
const auto candidate = Child(root, *part);
const auto status = std::filesystem::symlink_status(candidate, error);
if (std::filesystem::exists(status) || (error && error != std::errc::no_such_file_or_directory)) {
Expand Down
37 changes: 37 additions & 0 deletions core/libs/tests/GuestFilesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,42 @@ static void TestRelinkedModuleNameCase() {
}

#ifdef _WIN32
static void TestCaseSensitiveHostDirectory() {
const auto root = std::filesystem::temp_directory_path() / ("anyps5-sensitive-" +
std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
Require(std::filesystem::create_directory(root));
const auto handle = CreateFileW(root.c_str(), FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
Require(handle != INVALID_HANDLE_VALUE);
struct { ULONG flags; } information{FILE_CS_FLAG_CASE_SENSITIVE_DIR};
constexpr auto caseSensitiveInfo = static_cast<FILE_INFO_BY_HANDLE_CLASS>(23);
const bool enabled = SetFileInformationByHandle(handle, caseSensitiveInfo, &information, sizeof(information));
const auto error = GetLastError();
CloseHandle(handle);
if (!enabled && (error == ERROR_NOT_SUPPORTED || error == ERROR_INVALID_PARAMETER || error == ERROR_ACCESS_DENIED)) {
std::printf("case-sensitive host directory unavailable: %lu\n", error);
std::filesystem::remove(root);
return;
}
Require(enabled);
{ std::ofstream file(root / "File.txt"); file << "payload"; }
Require(std::filesystem::create_directory(root / "Sub"));
Require(std::filesystem::create_directory(root / "Both"));
Require(std::filesystem::create_directory(root / "both"));
{ std::ofstream file(root / "libCase.prx.guest.prx"); file << "module"; }
const auto host = root.string();
AddPathAlias_nid_no_patch("/sensitive-host", host.c_str());
Require(ResolvePath_nid_no_patch("/sensitive-host/file.txt") == root / "File.txt");
Require(ResolvePath_nid_no_patch("/sensitive-host/sub") == root / "Sub");
Require(ResolvePathOrRelinkedModule_nid_no_patch("/sensitive-host/libcase.prx") == root / "libCase.prx.guest.prx");
bool ambiguous = false;
try { ResolvePath_nid_no_patch("/sensitive-host/BOTH/file.txt"); }
catch (const std::runtime_error& failure) { ambiguous = std::strstr(failure.what(), "Ambiguous case-insensitive guest path") != nullptr; }
Require(ambiguous);
RemovePathAlias_nid_no_patch("/sensitive-host");
std::filesystem::remove_all(root);
}

static std::filesystem::path ExtendedPath(const std::filesystem::path& path) {
const auto& native = path.native();
if (native.starts_with(L"\\\\?\\") || native.starts_with(L"\\\\.\\")) return path;
Expand Down Expand Up @@ -343,6 +379,7 @@ int main() {
TestRelinkedModuleNames();
TestRelinkedModuleNameCase();
#ifdef _WIN32
TestCaseSensitiveHostDirectory();
TestLongHostPathStat();
#endif
#ifdef __linux__
Expand Down
Loading