From 4dc65b0c6f40746db22cd45dcada465ca5801805 Mon Sep 17 00:00:00 2001 From: Gruzzberry Date: Sun, 25 Oct 2026 21:34:22 +0000 Subject: [PATCH] fix(libkernel): lutimes reports ENOTDIR for a path through a file Co-Authored-By: Claude Sonnet 5.5 --- core/libs/prx/libkernel/File/src/Stdio.cpp | 2 +- core/libs/tests/GuestFileTimestamps.cpp | 37 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index bf22e21d02b5..e1aeeed5cc01 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -1669,7 +1669,7 @@ int APS5_VABI lutimes_nid_postfix(const char* path, const KernelTimeval* times) path = copy.c_str(); std::filesystem::path native; if (const int error = ResolvePathError_nid_no_patch(path, native)) return PosixFailure(error); - if (NativeUtimes(native, times, false) != 0) return PosixResult(SceErrorFromErrno(errno)); + if (NativeUtimes(native, times, false) != 0) return PosixResult(SceErrorFromErrno(errno == ENOENT ? NotFoundError_nid_no_patch(native) : errno)); return 0; } diff --git a/core/libs/tests/GuestFileTimestamps.cpp b/core/libs/tests/GuestFileTimestamps.cpp index 983915404c75..2af9c7670f86 100644 --- a/core/libs/tests/GuestFileTimestamps.cpp +++ b/core/libs/tests/GuestFileTimestamps.cpp @@ -257,5 +257,42 @@ int main() { Require(futimes_nid_postfix(pipe[0], first) == -1 && *__error_nid_postfix() == 22, "pipe timestamp update fails"); #endif Require(close_nid_postfix(pipe[0]) == 0 && close_nid_postfix(pipe[1]) == 0, "close pipe"); + const auto throughFile = (root / "file.txt" / "child").string(); + for (const auto update : {utimes_nid_postfix, lutimes_nid_postfix}) { + Require(update(throughFile.c_str(), first) == -1 && *__error_nid_postfix() == 20, "a path through a file reports ENOTDIR"); + } +#ifdef _WIN32 + const auto fileLink = (root / "file-link.txt").string(); + const auto directoryLink = (root / "directory-link").string(); + const auto danglingLink = (root / "dangling-link").string(); + const auto createLink = [](const std::string& link, const std::string& target, DWORD flags) { + wchar_t full[MAX_PATH]; + GetFullPathNameW(std::filesystem::path(target).c_str(), MAX_PATH, full, nullptr); + return CreateSymbolicLinkW(std::filesystem::path(link).c_str(), full, flags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0; + }; + if (createLink(fileLink, path, 0) && createLink(directoryLink, directory, SYMBOLIC_LINK_FLAG_DIRECTORY) && + createLink(danglingLink, (root / "missing").string(), 0)) { + const auto modified = [](const char* name, bool link) { + FileStat status{}; + Require((link ? lstat_nid_postfix(name, &status) : stat_nid_postfix(name, &status)) == 0, "stat a link fixture"); + return status.st_mtim.tv_sec; + }; + Require(utimes_nid_postfix(fileLink.c_str(), first) == 0 && modified(path.c_str(), false) == first[1].tv_sec, "utimes follows a file link"); + Require(modified(fileLink.c_str(), true) != first[1].tv_sec, "utimes leaves the file link alone"); + Require(utimes_nid_postfix(directoryLink.c_str(), second) == 0 && modified(directory.c_str(), false) == second[1].tv_sec, + "utimes follows a directory link"); + Require(utimes_nid_postfix(danglingLink.c_str(), first) == -1 && *__error_nid_postfix() == 2, "utimes through a dangling link reports ENOENT"); + Require(lutimes_nid_postfix(directoryLink.c_str(), first) == 0 && modified(directoryLink.c_str(), true) == first[1].tv_sec && + modified(directory.c_str(), false) == second[1].tv_sec, "lutimes changes a directory link and not its target"); + Require(lutimes_nid_postfix(danglingLink.c_str(), first) == 0 && modified(danglingLink.c_str(), true) == first[1].tv_sec, + "lutimes changes a dangling link"); + const auto throughLink = fileLink + "/child"; + for (const auto update : {utimes_nid_postfix, lutimes_nid_postfix}) { + Require(update(throughLink.c_str(), first) == -1 && *__error_nid_postfix() == 20, "a path through a file link reports ENOTDIR"); + } + Require(RemoveDirectoryW(std::filesystem::path(directoryLink).c_str()) && DeleteFileW(std::filesystem::path(fileLink).c_str()) && + DeleteFileW(std::filesystem::path(danglingLink).c_str()), "remove the link fixtures"); + } +#endif std::filesystem::remove_all(root); }