From 69b9d186db06c87b8f21c9e8909de1771b639036 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 12 Oct 2026 14:26:19 +0400 Subject: [PATCH] fix(libc): replace an open or read-only destination in rename on Windows --- .../libs/prx/libc/include/FilesystemError.hpp | 2 + core/libs/prx/libc/src/Rename.cpp | 39 ++++++++++++++++++- core/libs/prx/libkernel/File/src/Stdio.cpp | 10 ++++- core/libs/tests/GuestFilesystem.cpp | 17 ++++++++ 4 files changed, 64 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libc/include/FilesystemError.hpp b/core/libs/prx/libc/include/FilesystemError.hpp index 4bec836c8..8962c34b1 100644 --- a/core/libs/prx/libc/include/FilesystemError.hpp +++ b/core/libs/prx/libc/include/FilesystemError.hpp @@ -23,6 +23,8 @@ inline int FilesystemError(const std::error_code& error) { return 5; } +std::error_code RenameNative_nid_no_patch(const std::filesystem::path& source, const std::filesystem::path& destination, bool replacesFile); + inline int ResolvePathError_nid_no_patch(const char* path, std::filesystem::path& resolved) { try { resolved = ResolvePath_nid_no_patch(path); diff --git a/core/libs/prx/libc/src/Rename.cpp b/core/libs/prx/libc/src/Rename.cpp index b5bb04527..1eac68095 100644 --- a/core/libs/prx/libc/src/Rename.cpp +++ b/core/libs/prx/libc/src/Rename.cpp @@ -3,6 +3,42 @@ #include #include #include +#ifdef _WIN32 +#include +#include +#include +#include +#endif + +std::error_code RenameNative_nid_no_patch(const std::filesystem::path& source, const std::filesystem::path& destination, [[maybe_unused]] bool replacesFile) { + namespace fs = std::filesystem; + std::error_code error; +#ifdef _WIN32 + if (replacesFile) { + const auto target = fs::absolute(destination, error); + if (error) return error; + struct RenameInfo { DWORD flags; HANDLE root; DWORD nameBytes; WCHAR name[1]; }; + static_assert(sizeof(RenameInfo) == sizeof(FILE_RENAME_INFO) && offsetof(RenameInfo, name) == offsetof(FILE_RENAME_INFO, FileName)); + if (target.native().size() > (MAXDWORD - sizeof(RenameInfo)) / sizeof(wchar_t)) return std::make_error_code(std::errc::filename_too_long); + const auto nameBytes = target.native().size() * sizeof(wchar_t); + std::vector buffer(sizeof(RenameInfo) + nameBytes); + auto* info = reinterpret_cast(buffer.data()); + constexpr DWORD replaceExisting = 0x00000001, posixSemantics = 0x00000002, ignoreReadOnly = 0x00000040; + const auto attributes = GetFileAttributesW(destination.c_str()); + info->flags = replaceExisting | posixSemantics | (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_READONLY) ? ignoreReadOnly : 0); + info->nameBytes = static_cast(nameBytes); + std::memcpy(info->name, target.c_str(), nameBytes); + const auto handle = CreateFileW(source.c_str(), DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, nullptr); + if (handle == INVALID_HANDLE_VALUE) return std::error_code(static_cast(GetLastError()), std::system_category()); + constexpr auto renameInfoEx = static_cast(22); + if (!SetFileInformationByHandle(handle, renameInfoEx, info, static_cast(buffer.size()))) error = std::error_code(static_cast(GetLastError()), std::system_category()); + CloseHandle(handle); + return error; + } +#endif + fs::rename(source, destination, error); + return error; +} static int RenamePath_nid_no_patch(const std::filesystem::path& source, const std::filesystem::path& destination) { constexpr int GuestEnoent = 2; @@ -61,8 +97,7 @@ static int RenamePath_nid_no_patch(const std::filesystem::path& source, const st } #endif - error.clear(); - fs::rename(source, destination, error); + error = RenameNative_nid_no_patch(source, destination, destinationExists && !sourceIsDirectory && !destinationIsDirectory); return error ? FilesystemError(error) : 0; } diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index 1c5a227a9..e33d7386b 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -1102,10 +1102,16 @@ int APS5_VABI sceKernelRename(const char* from, const char* to) { std::filesystem::path source; if (const int error = ResolvePathError_nid_no_patch(from, source)) return SceKernelError(error); std::error_code error; - if (!std::filesystem::exists(source, error)) return SceErrorFromErrno(GUEST_ENOENT); + const auto sourceStatus = std::filesystem::status(source, error); + if (!std::filesystem::exists(sourceStatus)) return SceErrorFromErrno(GUEST_ENOENT); std::filesystem::path destination; if (const int result = ResolvePathError_nid_no_patch(to, destination)) return SceKernelError(result); - std::filesystem::rename(source, destination, error); + bool replacesFile = false; +#ifdef _WIN32 + const auto destinationStatus = std::filesystem::symlink_status(destination, error); + replacesFile = !std::filesystem::is_directory(sourceStatus) && std::filesystem::exists(destinationStatus) && !std::filesystem::is_directory(destinationStatus); +#endif + error = RenameNative_nid_no_patch(source, destination, replacesFile); if (error) return SceErrorFromErrno(GUEST_EIO); RecordWrittenPath_nid_no_patch(source); RecordWrittenPath_nid_no_patch(destination); diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index d32843235..f52c5ca45 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -328,6 +328,23 @@ int main() { Require(rename_nid_postfix(name(targetFile).c_str(), name(targetFile).c_str()) == 0); Require(rename_nid_postfix(name(sourceDirectory).c_str(), name(emptyDirectory).c_str()) == 0); Require(!std::filesystem::exists(sourceDirectory) && std::filesystem::is_directory(emptyDirectory / "child")); + for (const bool kernel : {false, true}) { + const auto incoming = name(area / "incoming.txt"); + const auto occupied = name(area / "occupied.txt"); + const auto replace = [&] { return kernel ? sceKernelRename(incoming.c_str(), occupied.c_str()) : rename_nid_postfix(incoming.c_str(), occupied.c_str()); }; + const auto contents = [&] { std::ifstream stream(occupied); std::string text; std::getline(stream, text); return text; }; + { std::ofstream stream(incoming); stream << "incoming"; } + { std::ofstream stream(occupied); stream << "occupied"; } + const int occupiedDescriptor = sceKernelOpen(occupied.c_str(), 2, 0); + Require(occupiedDescriptor >= 0 && replace() == 0 && !std::filesystem::exists(incoming) && contents() == "incoming"); + char occupiedBytes[8]{}; + Require(read_nid_postfix(occupiedDescriptor, occupiedBytes, sizeof(occupiedBytes)) == sizeof(occupiedBytes) && std::memcmp(occupiedBytes, "occupied", 8) == 0); + Require(sceKernelClose(occupiedDescriptor) == 0 && contents() == "incoming"); + { std::ofstream stream(incoming); stream << "replaced"; } + Require(sceKernelChmod_nid_postfix(occupied.c_str(), 0400) == 0); + Require(replace() == 0 && !std::filesystem::exists(incoming) && contents() == "replaced"); + Require(remove_nid_postfix(occupied.c_str()) == 0); + } #ifndef _WIN32 const auto dangling = area / "dangling"; std::filesystem::create_symlink(area / "nonexistent", dangling);