From dc022d8418fbdbfe90535a310979f51292b37392 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 11 Oct 2026 10:27:19 +0400 Subject: [PATCH] fix(libkernel): let a guest unlink and rename files it has open on Windows FreeBSD removes or renames a file that is still open; the descriptor stays usable. _wopen opens without FILE_SHARE_DELETE, so sceKernelUnlink returned EACCES and rename failed while the guest held a descriptor. sceKernelOpen now opens through CreateFileW with delete sharing and keeps the flag, mode and error mapping of _wopen. (cherry picked from commit 2f5ada5c86a427505804f22def209e054b3571e4) --- core/libs/prx/libkernel/File/src/Open.cpp | 49 ++++++++++++++++++++++- core/libs/tests/GuestFilesystem.cpp | 15 +++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libkernel/File/src/Open.cpp b/core/libs/prx/libkernel/File/src/Open.cpp index ca7cbe5f1..02b338d7d 100644 --- a/core/libs/prx/libkernel/File/src/Open.cpp +++ b/core/libs/prx/libkernel/File/src/Open.cpp @@ -20,8 +20,55 @@ #include #include #include +#include +static int OpenError(DWORD error) { + switch (error) { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + case ERROR_INVALID_DRIVE: + case ERROR_BAD_NETPATH: + case ERROR_BAD_NET_NAME: + case ERROR_BAD_PATHNAME: + case ERROR_FILENAME_EXCED_RANGE: + return ENOENT; + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: + case ERROR_LOCK_VIOLATION: + case ERROR_WRITE_PROTECT: + case ERROR_NETWORK_ACCESS_DENIED: + case ERROR_CANNOT_MAKE: + case ERROR_DRIVE_LOCKED: + return EACCES; + case ERROR_FILE_EXISTS: + case ERROR_ALREADY_EXISTS: + return EEXIST; + case ERROR_TOO_MANY_OPEN_FILES: + return EMFILE; + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_OUTOFMEMORY: + return ENOMEM; + case ERROR_DISK_FULL: + return ENOSPC; + default: + return EINVAL; + } +} static int NativeOpen(const std::filesystem::path& p, int nativeFlags, std::uint16_t mode) { - return ::_wopen(p.wstring().c_str(), nativeFlags, static_cast(mode)); + const int access = nativeFlags & (_O_RDONLY | _O_WRONLY | _O_RDWR); + const DWORD desired = access == _O_RDONLY ? GENERIC_READ : access == _O_WRONLY ? GENERIC_WRITE : GENERIC_READ | GENERIC_WRITE; + DWORD disposition = OPEN_EXISTING; + if (nativeFlags & _O_CREAT) disposition = (nativeFlags & _O_EXCL) ? CREATE_NEW : (nativeFlags & _O_TRUNC) ? CREATE_ALWAYS : OPEN_ALWAYS; + else if (nativeFlags & _O_TRUNC) disposition = TRUNCATE_EXISTING; + const DWORD attributes = (nativeFlags & _O_CREAT) && !(mode & _S_IWRITE) ? FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL; + SECURITY_ATTRIBUTES security{sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE}; + const auto handle = CreateFileW(p.c_str(), desired, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &security, disposition, attributes, nullptr); + if (handle == INVALID_HANDLE_VALUE) { + errno = OpenError(GetLastError()); + return -1; + } + const int descriptor = ::_open_osfhandle(reinterpret_cast(handle), nativeFlags & (_O_APPEND | _O_RDONLY | _O_BINARY)); + if (descriptor < 0) CloseHandle(handle); + return descriptor; } static std::int64_t NativeLseek(int fd, std::int64_t offset, int whence) { return ::_lseeki64(fd, offset, whence); diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index b741a61b1..d7b02ceef 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -136,6 +136,21 @@ int main() { Require(mkdir_nid_postfix(nullptr, 0700) == -1 && *__error_nid_postfix() == 14); const auto file = root / "file.txt"; { std::ofstream stream(file); stream << "retained until removal"; } + const auto held = root / "held.txt"; + const auto heldName = held.string(); + { std::ofstream stream(held); stream << "held"; } + const int heldDescriptor = sceKernelOpen(heldName.c_str(), 2, 0); + Require(heldDescriptor >= 0 && sceKernelUnlink(heldName.c_str()) == 0 && !std::filesystem::exists(held)); + char heldBytes[4]{}; + Require(read_nid_postfix(heldDescriptor, heldBytes, sizeof(heldBytes)) == sizeof(heldBytes) && std::memcmp(heldBytes, "held", 4) == 0); + const int recreated = sceKernelOpen(heldName.c_str(), 0x0a01, 0644); + Require(recreated >= 0 && sceKernelClose(heldDescriptor) == 0 && std::filesystem::file_size(held) == 0); + const auto moved = root / "moved.txt"; + Require(rename_nid_postfix(heldName.c_str(), moved.string().c_str()) == 0 && !std::filesystem::exists(held)); + Require(write_nid_postfix(recreated, "moved", 5) == 5 && sceKernelClose(recreated) == 0 && std::filesystem::file_size(moved) == 5); + const int removing = open_nid_postfix(moved.string().c_str(), 0, 0); + Require(removing >= 0 && remove_nid_postfix(moved.string().c_str()) == 0 && !std::filesystem::exists(moved)); + Require(close_nid_postfix(removing) == 0); Require(remove_nid_postfix(root.string().c_str()) == -1); Require(*__error_nid_postfix() == 66); Require(std::filesystem::is_regular_file(file));