diff --git a/core/libs/prx/libc/include/General.hpp b/core/libs/prx/libc/include/General.hpp index 8613f4cad..2a2a9ed92 100644 --- a/core/libs/prx/libc/include/General.hpp +++ b/core/libs/prx/libc/include/General.hpp @@ -15,6 +15,7 @@ extern "C" bool StopAtPrefixLetter_nid_no_patch(const char* str, char** endptr, extern "C" std::filesystem::path ResolvePath_nid_no_patch(const char* path); extern "C" int SetWorkingDirectory_nid_no_patch(const char* resolved); +extern "C" bool FinalPathComponentIsDot_nid_no_patch(const char* path); // Guest path aliases: a guest prefix such as "/savedata0" (a save-data mount point) resolves to a host // directory instead of the run directory; the prefix matches whole path components only. extern "C" void AddPathAlias_nid_no_patch(const char* guestPrefix, const char* hostPath); diff --git a/core/libs/prx/libc/src/Filesystem.cpp b/core/libs/prx/libc/src/Filesystem.cpp index d59f14d39..cff29e87e 100644 --- a/core/libs/prx/libc/src/Filesystem.cpp +++ b/core/libs/prx/libc/src/Filesystem.cpp @@ -75,6 +75,15 @@ extern "C" int APS5_VABI remove_nid_postfix(const char* path) { if (!*path) { errno = 2; return -1; } try { const auto resolved = ResolvePath_nid_no_patch(path); + if (FinalPathComponentIsDot_nid_no_patch(path)) { + std::error_code statusError; + const auto status = std::filesystem::status(resolved, statusError); + if (statusError) { errno = FilesystemError(statusError); return -1; } + if (!std::filesystem::exists(status)) { errno = 2; return -1; } + if (!std::filesystem::is_directory(status)) { errno = 20; return -1; } + errno = 22; + return -1; + } std::error_code error; #ifdef _WIN32 const DWORD attributes = GetFileAttributesW(resolved.c_str()); diff --git a/core/libs/prx/libc/src/General.cpp b/core/libs/prx/libc/src/General.cpp index 3397c6080..370ff3f13 100644 --- a/core/libs/prx/libc/src/General.cpp +++ b/core/libs/prx/libc/src/General.cpp @@ -328,6 +328,14 @@ extern "C" int SetWorkingDirectory_nid_no_patch(const char* resolved) { } } +extern "C" bool FinalPathComponentIsDot_nid_no_patch(const char* path) { + std::string_view text(path); + while (!text.empty() && (text.back() == '/' || text.back() == '\\')) text.remove_suffix(1); + const auto separator = text.find_last_of("/\\"); + const auto component = text.substr(separator == std::string_view::npos ? 0 : separator + 1); + return component == "."; +} + extern "C" int APS5_VABI chdir_nid_postfix(const char* path) { if (!path) { errno = 14; return -1; } if (!*path) { errno = 2; return -1; } diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index 7fbef5c28..74f056535 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -1018,6 +1018,7 @@ int APS5_VABI sceKernelRmdir(const char* path) { const bool blockedByFile = HasNonDirectoryPrefix(native) || std::filesystem::exists(WithoutTrailingSeparator(native), error); return SceErrorFromErrno(blockedByFile ? GUEST_ENOTDIR : GUEST_ENOENT); } + if (FinalPathComponentIsDot_nid_no_patch(path)) return SceErrorFromErrno(GUEST_EINVAL); if (!std::filesystem::is_empty(native, error)) return SceErrorFromErrno(GUEST_ENOTEMPTY); if (!RemoveEmptyDirectory(native)) return SceErrorFromErrno(GUEST_EIO); RecordWrittenPath_nid_no_patch(native); diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index 4f2710df7..26b777f7b 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -460,6 +460,25 @@ int main() { Require(rmdir_nid_postfix((missingName + "/").c_str()) == -1 && *__error_nid_postfix() == 2); Require(sceKernelRmdir((presentName + "/").c_str()) == static_cast(0x80020014u)); Require(sceKernelRmdir((missingName + "/").c_str()) == static_cast(0x80020002u)); + const auto dotDirectory = root / "dot-directory"; + Require(std::filesystem::create_directory(dotDirectory)); + const auto dotDirectoryName = dotDirectory.string() + "/."; + const auto dotDirectoryTrailingName = dotDirectoryName + "/"; + Require(rmdir_nid_postfix(dotDirectoryName.c_str()) == -1 && *__error_nid_postfix() == 22); + Require(sceKernelRmdir(dotDirectoryName.c_str()) == static_cast(0x80020016u)); + Require(remove_nid_postfix(dotDirectoryName.c_str()) == -1 && *__error_nid_postfix() == 22); + Require(rmdir_nid_postfix(dotDirectoryTrailingName.c_str()) == -1 && *__error_nid_postfix() == 22); + Require(sceKernelRmdir(dotDirectoryTrailingName.c_str()) == static_cast(0x80020016u)); + Require(remove_nid_postfix(dotDirectoryTrailingName.c_str()) == -1 && *__error_nid_postfix() == 22); + Require(std::filesystem::is_directory(dotDirectory)); + const auto dotFileName = presentName + "/."; + Require(rmdir_nid_postfix(dotFileName.c_str()) == -1 && *__error_nid_postfix() == 20); + Require(sceKernelRmdir(dotFileName.c_str()) == static_cast(0x80020014u)); + Require(remove_nid_postfix(dotFileName.c_str()) == -1 && *__error_nid_postfix() == 20); + const auto dotMissingName = missingName + "/."; + Require(rmdir_nid_postfix(dotMissingName.c_str()) == -1 && *__error_nid_postfix() == 2); + Require(sceKernelRmdir(dotMissingName.c_str()) == static_cast(0x80020002u)); + Require(remove_nid_postfix(dotMissingName.c_str()) == -1 && *__error_nid_postfix() == 2); const auto missingParentLeaf = rootName + "/missing/leaf"; Require(sceKernelMkdir(missingParentLeaf.c_str(), 0700) == static_cast(0x80020002u)); const auto invalidNameUnderFile = presentName + "/a:b/x"; @@ -500,6 +519,7 @@ int main() { const auto empty = root / "empty"; Require(std::filesystem::create_directory(empty)); Require(rmdir_nid_postfix(empty.string().c_str()) == 0 && !std::filesystem::exists(empty)); + Require(rmdir_nid_postfix(dotDirectory.string().c_str()) == 0 && !std::filesystem::exists(dotDirectory)); Require(remove_nid_postfix(root.string().c_str()) == 0); Require(!std::filesystem::exists(root)); }