From c87f6202d309aaaccb66f14dd0a03a974b440b3d Mon Sep 17 00:00:00 2001 From: xbzk Date: Mon, 27 Jul 2026 21:54:55 -0300 Subject: [PATCH] [fs] Allow real VFS files to be replaced while open Add a Windows share-delete file open mode and use it for cached real VFS files. Close cached references before create, move, and delete so guest-side self-update flows can rename or replace files that Eden previously opened. Also preserve Android real VFS full paths so homebrew path derivation does not lose the original file path. --- src/common/fs/file.cpp | 63 +++++++++++++++++++++++++++++- src/common/fs/fs_types.h | 11 +++--- src/core/file_sys/vfs/vfs_real.cpp | 46 ++++++++++++++++++++-- src/core/file_sys/vfs/vfs_real.h | 2 + 4 files changed, 112 insertions(+), 10 deletions(-) diff --git a/src/common/fs/file.cpp b/src/common/fs/file.cpp index 461a5eee35..29ce4423d2 100644 --- a/src/common/fs/file.cpp +++ b/src/common/fs/file.cpp @@ -4,6 +4,8 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include #include #include "common/assert.h" @@ -15,8 +17,10 @@ #include "common/logging.h" #ifdef _WIN32 +#include #include #include +#include #else #include #endif @@ -95,10 +99,65 @@ namespace { case FileShareFlag::ShareWriteOnly: return _SH_DENYRD; case FileShareFlag::ShareReadWrite: + case FileShareFlag::ShareReadWriteDelete: return _SH_DENYNO; } } +[[nodiscard]] std::FILE* OpenWithWindowsShareDelete(const fs::path& path, FileAccessMode mode, + FileType type) { + DWORD desired_access{}; + DWORD creation_disposition{OPEN_EXISTING}; + int open_flags = type == FileType::BinaryFile ? _O_BINARY : _O_TEXT; + + switch (mode) { + case FileAccessMode::Read: + desired_access = GENERIC_READ; + open_flags |= _O_RDONLY; + break; + case FileAccessMode::Write: + desired_access = GENERIC_WRITE; + creation_disposition = CREATE_ALWAYS; + open_flags |= _O_WRONLY; + break; + case FileAccessMode::Append: + desired_access = GENERIC_WRITE; + creation_disposition = OPEN_ALWAYS; + open_flags |= _O_WRONLY | _O_APPEND; + break; + case FileAccessMode::ReadWrite: + desired_access = GENERIC_READ | GENERIC_WRITE; + open_flags |= _O_RDWR; + break; + case FileAccessMode::ReadAppend: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = OPEN_ALWAYS; + open_flags |= _O_RDWR | _O_APPEND; + break; + } + + const auto handle = + CreateFileW(path.c_str(), desired_access, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, + creation_disposition, FILE_ATTRIBUTE_NORMAL, nullptr); + if (handle == INVALID_HANDLE_VALUE) { + errno = EACCES; + return nullptr; + } + + const auto fd = _open_osfhandle(reinterpret_cast(handle), open_flags); + if (fd == -1) { + CloseHandle(handle); + return nullptr; + } + + auto* const file = _wfdopen(fd, AccessModeToWStr(mode, type)); + if (file == nullptr) { + _close(fd); + } + return file; +} + #else /** @@ -254,7 +313,9 @@ void IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, File errno = 0; #ifdef _WIN32 - if (flag != FileShareFlag::ShareNone) { + if (flag == FileShareFlag::ShareReadWriteDelete) { + file = OpenWithWindowsShareDelete(path, mode, type); + } else if (flag != FileShareFlag::ShareNone) { file = _wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag)); } else { _wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type)); diff --git a/src/common/fs/fs_types.h b/src/common/fs/fs_types.h index 7b7359fa6f..9c5cc188a4 100644 --- a/src/common/fs/fs_types.h +++ b/src/common/fs/fs_types.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -49,10 +49,11 @@ enum class FileType { }; enum class FileShareFlag { - ShareNone, // Provides exclusive access to the file. - ShareReadOnly, // Provides read only shared access to the file. - ShareWriteOnly, // Provides write only shared access to the file. - ShareReadWrite, // Provides read and write shared access to the file. + ShareNone, // Provides exclusive access to the file. + ShareReadOnly, // Provides read only shared access to the file. + ShareWriteOnly, // Provides write only shared access to the file. + ShareReadWrite, // Provides read and write shared access to the file. + ShareReadWriteDelete, // Provides read, write, and delete shared access to the file. }; enum class DirEntryFilter { diff --git a/src/core/file_sys/vfs/vfs_real.cpp b/src/core/file_sys/vfs/vfs_real.cpp index b04f832cd3..b47b053bfb 100644 --- a/src/core/file_sys/vfs/vfs_real.cpp +++ b/src/core/file_sys/vfs/vfs_real.cpp @@ -12,7 +12,6 @@ #include "common/fs/file.h" #include "common/fs/fs.h" #include "common/fs/path_util.h" -#include "common/logging.h" #include "core/file_sys/vfs/vfs.h" #include "core/file_sys/vfs/vfs_real.h" @@ -124,6 +123,7 @@ VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, OpenMode perms const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); { std::scoped_lock lk{list_lock}; + CloseCachedFileReferenceLocked(path); cache.erase(path); } @@ -157,6 +157,8 @@ VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_ const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); { std::scoped_lock lk{list_lock}; + CloseCachedFileReferenceLocked(old_path); + CloseCachedFileReferenceLocked(new_path); cache.erase(old_path); cache.erase(new_path); } @@ -170,6 +172,7 @@ bool RealVfsFilesystem::DeleteFile(std::string_view path_) { const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); { std::scoped_lock lk{list_lock}; + CloseCachedFileReferenceLocked(path); cache.erase(path); } return FS::RemoveFile(path); @@ -222,8 +225,8 @@ std::unique_lock RealVfsFilesystem::RefreshReference(const std::stri if (!reference.file) { this->EvictSingleReferenceLocked(); - reference.file = - FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile); + reference.file = FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), + FS::FileType::BinaryFile, FS::FileShareFlag::ShareReadWriteDelete); if (reference.file) { num_open_files++; } @@ -297,15 +300,50 @@ RealVfsFile::~RealVfsFile() { base.DropReference(std::move(reference)); } +void RealVfsFilesystem::CloseCachedFileReferenceLocked(const std::string& path) { + const auto it = cache.find(path); + if (it == cache.end()) { + return; + } + + const auto cached_file = it->second.lock(); + if (!cached_file) { + return; + } + + auto* real_file = static_cast(cached_file.get()); + auto& reference = real_file->reference; + if (!reference || !reference->file) { + return; + } + + RemoveReferenceFromListLocked(*reference); + reference->file.reset(); + num_open_files--; + InsertReferenceIntoListLocked(*reference); +} + std::string RealVfsFile::GetName() const { #ifdef __ANDROID__ - if (path[0] != '/') { + if (!path.empty() && path[0] != '/') { return FS::Android::GetFilename(path); } #endif return path_components.empty() ? "" : std::string(path_components.back()); } +std::string RealVfsFile::GetFullPath() const { +#ifdef __ANDROID__ + if (!path.empty() && path[0] != '/') { + auto out = path; + std::replace(out.begin(), out.end(), '\\', '/'); + return out; + } +#endif + + return VfsFile::GetFullPath(); +} + std::size_t RealVfsFile::GetSize() const { if (size) { return *size; diff --git a/src/core/file_sys/vfs/vfs_real.h b/src/core/file_sys/vfs/vfs_real.h index 672126d7ba..2dad801bbf 100644 --- a/src/core/file_sys/vfs/vfs_real.h +++ b/src/core/file_sys/vfs/vfs_real.h @@ -63,6 +63,7 @@ private: std::unique_lock RefreshReference(const std::string& path, OpenMode perms, FileReference& reference); void DropReference(std::unique_ptr&& reference); + void CloseCachedFileReferenceLocked(const std::string& path); private: friend class RealVfsDirectory; @@ -85,6 +86,7 @@ public: ~RealVfsFile() override; std::string GetName() const override; + std::string GetFullPath() const override; std::size_t GetSize() const override; bool Resize(std::size_t new_size) override; VirtualDir GetContainingDirectory() const override;