From 720cf7e296ae0fbd80f42282551584e296cb36ff Mon Sep 17 00:00:00 2001 From: Gruzzberry Date: Wed, 21 Oct 2026 23:50:49 +0100 Subject: [PATCH 1/2] fix(libkernel): fail chflags through a dangling Windows symbolic link with ENOENT std::filesystem::status does not follow a Windows symbolic link under MinGW, so the lookup chflags makes saw a link whose target is missing as an existing file and returned 0. A reparse point in the last component is now looked up through the target with the guest stat, as FreeBSD 11.4's namei follows it: a missing target gives ENOENT, and a trailing slash after a link to a file gives ENOTDIR. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libkernel/File/src/Stdio.cpp | 9 +++++++++ core/libs/tests/GuestFilesystem.cpp | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index e0a94a2c44..18b6380c85 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -662,6 +662,15 @@ int File::DuplicateFrom(int fd, int start) { } static int LookupError(const std::filesystem::path& native) { +#ifdef _WIN32 + const auto named = native.has_filename() ? native : WithoutTrailingSeparator(native); + const DWORD attributes = GetFileAttributesW(named.c_str()); + if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { + FileStat target{}; + if (!File::TryFillFileStat(named, &target)) return SceErrorFromErrno(errno) & 0xffff; + return native.has_filename() || (target.st_mode & 0170000) == 0040000 ? 0 : GUEST_ENOTDIR; + } +#endif std::error_code error; const auto status = std::filesystem::status(native, error); if (std::filesystem::exists(status)) return native.has_filename() || std::filesystem::is_directory(status) ? 0 : GUEST_ENOTDIR; diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index 18bd6daaf7..4edd692934 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -634,6 +634,27 @@ int main() { Require(chflags_nid_postfix(file.string().c_str(), 0x200000) == -1 && *__error_nid_postfix() == 1); Require(ChflagsThrows(file, 0x2, "UF_IMMUTABLE")); Require(ChflagsThrows(file, 0x1000 | 0x40000, "UF_READONLY|SF_APPEND")); + const auto dangling = root / "dangling.link"; + const auto pointing = root / "file.link"; +#ifdef _WIN32 + const bool linked = CreateSymbolicLinkW(dangling.wstring().c_str(), L"missing.bin", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0 && + CreateSymbolicLinkW(pointing.wstring().c_str(), L"file.txt", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0; +#else + std::error_code linkError; + std::filesystem::create_symlink("missing.bin", dangling, linkError); + if (!linkError) std::filesystem::create_symlink("file.txt", pointing, linkError); + const bool linked = !linkError; +#endif + if (linked) { + Require(chflags_nid_postfix(dangling.string().c_str(), 0) == -1 && *__error_nid_postfix() == 2); + Require(chflags_nid_postfix(pointing.string().c_str(), 0) == 0); + Require(chflags_nid_postfix((pointing.string() + "/").c_str(), 0) == -1 && *__error_nid_postfix() == 20); + Require(chflags_nid_postfix(pointing.string().c_str(), 0x20) == -1 && *__error_nid_postfix() == 45); + Require(unlink_nid_postfix(dangling.string().c_str()) == 0 && unlink_nid_postfix(pointing.string().c_str()) == 0); + } else { + unlink_nid_postfix(dangling.string().c_str()); + std::fprintf(stderr, "symbolic links unavailable: the chflags link cases not tested\n"); + } const int flagged = open_nid_postfix(file.string().c_str(), 0, 0); Require(flagged >= 0 && fchflags_nid_postfix(flagged, 0) == 0); Require(fchflags_nid_postfix(flagged, 0x20) == -1 && *__error_nid_postfix() == 45); From a5c72e02c45ec5e9807e2014915a1dafdace08fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 25 Oct 2026 17:56:56 +0100 Subject: [PATCH 2/2] fix(libkernel): rename the chflags link test locals that clash in main() Co-Authored-By: Claude Sonnet 5.5 --- core/libs/tests/GuestFilesystem.cpp | 30 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index 4edd692934..61d5d41d79 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -634,25 +634,25 @@ int main() { Require(chflags_nid_postfix(file.string().c_str(), 0x200000) == -1 && *__error_nid_postfix() == 1); Require(ChflagsThrows(file, 0x2, "UF_IMMUTABLE")); Require(ChflagsThrows(file, 0x1000 | 0x40000, "UF_READONLY|SF_APPEND")); - const auto dangling = root / "dangling.link"; - const auto pointing = root / "file.link"; + const auto chflagsDangling = root / "dangling.link"; + const auto chflagsPointing = root / "file.link"; #ifdef _WIN32 - const bool linked = CreateSymbolicLinkW(dangling.wstring().c_str(), L"missing.bin", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0 && - CreateSymbolicLinkW(pointing.wstring().c_str(), L"file.txt", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0; + const bool chflagsLinked = CreateSymbolicLinkW(chflagsDangling.wstring().c_str(), L"missing.bin", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0 && + CreateSymbolicLinkW(chflagsPointing.wstring().c_str(), L"file.txt", SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0; #else - std::error_code linkError; - std::filesystem::create_symlink("missing.bin", dangling, linkError); - if (!linkError) std::filesystem::create_symlink("file.txt", pointing, linkError); - const bool linked = !linkError; + std::error_code chflagsLinkError; + std::filesystem::create_symlink("missing.bin", chflagsDangling, chflagsLinkError); + if (!chflagsLinkError) std::filesystem::create_symlink("file.txt", chflagsPointing, chflagsLinkError); + const bool chflagsLinked = !chflagsLinkError; #endif - if (linked) { - Require(chflags_nid_postfix(dangling.string().c_str(), 0) == -1 && *__error_nid_postfix() == 2); - Require(chflags_nid_postfix(pointing.string().c_str(), 0) == 0); - Require(chflags_nid_postfix((pointing.string() + "/").c_str(), 0) == -1 && *__error_nid_postfix() == 20); - Require(chflags_nid_postfix(pointing.string().c_str(), 0x20) == -1 && *__error_nid_postfix() == 45); - Require(unlink_nid_postfix(dangling.string().c_str()) == 0 && unlink_nid_postfix(pointing.string().c_str()) == 0); + if (chflagsLinked) { + Require(chflags_nid_postfix(chflagsDangling.string().c_str(), 0) == -1 && *__error_nid_postfix() == 2); + Require(chflags_nid_postfix(chflagsPointing.string().c_str(), 0) == 0); + Require(chflags_nid_postfix((chflagsPointing.string() + "/").c_str(), 0) == -1 && *__error_nid_postfix() == 20); + Require(chflags_nid_postfix(chflagsPointing.string().c_str(), 0x20) == -1 && *__error_nid_postfix() == 45); + Require(unlink_nid_postfix(chflagsDangling.string().c_str()) == 0 && unlink_nid_postfix(chflagsPointing.string().c_str()) == 0); } else { - unlink_nid_postfix(dangling.string().c_str()); + unlink_nid_postfix(chflagsDangling.string().c_str()); std::fprintf(stderr, "symbolic links unavailable: the chflags link cases not tested\n"); } const int flagged = open_nid_postfix(file.string().c_str(), 0, 0);