From 7cba4ce60ab441ca33c1b725fc3818cb9199a9b9 Mon Sep 17 00:00:00 2001 From: milescoviello Date: Sun, 11 Oct 2026 10:25:31 -0700 Subject: [PATCH] fix(libkernel_sys): let link follow a symbolic link like FreeBSD link threw "a symbolic link as the existing path is not supported" when its first argument named a symbolic link, so a valid call aborted the title. FreeBSD's link follows that link (sys/kern/vfs_syscalls.c, sys_link: "kern_linkat(td, AT_FDCWD, AT_FDCWD, uap->path, uap->link, UIO_USERSPACE, FOLLOW)") and hard-links the file it points to; a link to a directory then fails with EPERM ("if (vp->v_type == VDIR) ... return (EPERM);") and a dangling one with ENOENT from namei. link now checks the followed status of the existing path and, when it is a symbolic link, creates the hard link to the file it resolves to. std::filesystem::create_hard_link would otherwise link the symbolic link itself on Linux. Co-Authored-By: Claude Opus 5.5 (cherry picked from commit a521101b77c879234308711c5ec8a9f9cf00816f) --- core/libs/prx/libkernel_sys/Export.cpp | 6 +++--- core/libs/tests/GuestKernelSys.cpp | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index fee23a918..81cd9b2d0 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -264,13 +264,13 @@ int APS5_VABI link_nid_postfix(const char* existing, const char* created) { if (const int error = ResolvePathError_nid_no_patch(existing, source)) return static_cast(Fail(error)); if (const int error = ResolvePathError_nid_no_patch(created, destination)) return static_cast(Fail(error)); std::error_code error; - const auto status = std::filesystem::symlink_status(source, error); + const auto status = std::filesystem::status(source, error); if (!std::filesystem::exists(status)) return static_cast(Fail(GuestEnoent)); - if (std::filesystem::is_symlink(status)) throw std::runtime_error("link: a symbolic link as the existing path is not supported"); if (std::filesystem::is_directory(status)) return static_cast(Fail(GuestEperm)); if (std::filesystem::exists(std::filesystem::symlink_status(destination, error))) return static_cast(Fail(GuestEexist)); if (!std::filesystem::is_directory(destination.parent_path(), error)) return static_cast(Fail(GuestEnoent)); - std::filesystem::create_hard_link(source, destination, error); + const auto target = std::filesystem::is_symlink(source, error) ? std::filesystem::canonical(source, error) : source; + if (!error) std::filesystem::create_hard_link(target, destination, error); if (error == std::errc::cross_device_link) return static_cast(Fail(GuestExdev)); if (error) throw std::system_error(error, "link"); RecordWrittenPath_nid_no_patch(destination); diff --git a/core/libs/tests/GuestKernelSys.cpp b/core/libs/tests/GuestKernelSys.cpp index 8f5a1b913..7d7484e79 100644 --- a/core/libs/tests/GuestKernelSys.cpp +++ b/core/libs/tests/GuestKernelSys.cpp @@ -297,6 +297,20 @@ int main() { Require(openat_nid_postfix(base, nullptr, SCE_KERNEL_O_RDONLY) == -1 && Error() == 14); Require(openat_nid_postfix(base, "", SCE_KERNEL_O_RDONLY) == -1 && Error() == 2); Require(sceKernelClose(base) == 0); +#ifndef _WIN32 + if (created == 0) { + const auto throughLink = (root / "through-link.bin").string(); + Require(link_nid_postfix(symbolic.c_str(), throughLink.c_str()) == 0); + Require(!std::filesystem::is_symlink(std::filesystem::symlink_status(throughLink))); + Require(std::filesystem::equivalent(throughLink, original) && std::filesystem::hard_link_count(original) == 2); + Require(std::filesystem::remove(throughLink) && std::filesystem::hard_link_count(original) == 1); + Require(link_nid_postfix((root / "dangling").string().c_str(), throughLink.c_str()) == -1 && Error() == 2); + const auto directoryLink = (root / "directory-link").string(); + Require(symlink_nid_postfix(".", directoryLink.c_str()) == 0); + Require(link_nid_postfix(directoryLink.c_str(), throughLink.c_str()) == -1 && Error() == 1); + Require(!std::filesystem::exists(std::filesystem::symlink_status(throughLink))); + } +#endif std::filesystem::remove_all(root); return 0; }