From e8cb97e6e2c47d8439358dcfca658bbb4d32d596 Mon Sep 17 00:00:00 2001 From: milescoviello Date: Sun, 11 Oct 2026 09:44:11 -0700 Subject: [PATCH] fix(libkernel): fail rmdir on a symbolic link instead of removing it sceKernelRmdir, and rmdir through it, checked the path with std::filesystem::is_directory and is_empty, which follow a symbolic link, and then removed the path itself. So rmdir on a link to an empty directory returned 0 and deleted the link, while the target stayed. FreeBSD looks up the last component of rmdir's path without following it (sys/kern/vfs_syscalls.c, kern_rmdirat: NDINIT_ATRIGHTS with LOCKPARENT | LOCKLEAF and no FOLLOW) and then fails with ENOTDIR for anything that is not a directory: "if (vp->v_type != VDIR) { error = ENOTDIR; goto out; }". A link, whether it points to a directory or nowhere, is a VLNK there. rmdir now returns ENOTDIR for a symbolic link and leaves it alone, as unlink already looks at the link itself through symlink_status. A trailing slash still resolves through the link, as before. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libkernel/File/src/Stdio.cpp | 1 + core/libs/tests/GuestFilesystem.cpp | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index d383ac3c8..d17028cac 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -1028,6 +1028,7 @@ int APS5_VABI sceKernelRmdir(const char* path) { std::filesystem::path native; if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error); std::error_code error; + if (std::filesystem::is_symlink(native, error)) return SceErrorFromErrno(GUEST_ENOTDIR); if (!std::filesystem::is_directory(native, error)) { const bool blockedByFile = HasNonDirectoryPrefix(native) || std::filesystem::exists(WithoutTrailingSeparator(native), error); return SceErrorFromErrno(blockedByFile ? GUEST_ENOTDIR : GUEST_ENOENT); diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index 5474dee06..885b99ce8 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -580,6 +580,20 @@ 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)); +#ifndef _WIN32 + const auto linkedDirectory = root / "linked-directory"; + const auto directoryLink = root / "directory-link"; + const auto danglingDirectoryLink = root / "dangling-directory-link"; + Require(std::filesystem::create_directory(linkedDirectory)); + std::filesystem::create_directory_symlink("linked-directory", directoryLink); + std::filesystem::create_directory_symlink("missing-directory", danglingDirectoryLink); + Require(rmdir_nid_postfix(directoryLink.string().c_str()) == -1 && *__error_nid_postfix() == 20); + Require(sceKernelRmdir(directoryLink.string().c_str()) == static_cast(0x80020014u)); + Require(rmdir_nid_postfix(danglingDirectoryLink.string().c_str()) == -1 && *__error_nid_postfix() == 20); + Require(std::filesystem::is_symlink(directoryLink) && std::filesystem::is_directory(linkedDirectory)); + Require(unlink_nid_postfix(directoryLink.string().c_str()) == 0 && unlink_nid_postfix(danglingDirectoryLink.string().c_str()) == 0); + Require(rmdir_nid_postfix(linkedDirectory.string().c_str()) == 0 && !std::filesystem::exists(linkedDirectory)); +#endif const auto dotDirectory = root / "dot-directory"; Require(std::filesystem::create_directory(dotDirectory)); const auto dotDirectoryName = dotDirectory.string() + "/.";