diff options
| -rw-r--r-- | src/core/file_sys/vfs_real.cpp | 187 | ||||
| -rw-r--r-- | src/core/file_sys/vfs_real.h | 30 |
2 files changed, 118 insertions, 99 deletions
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index cc0076238..7a15d8438 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp | |||
| @@ -25,6 +25,8 @@ namespace FS = Common::FS; | |||
| 25 | 25 | ||
| 26 | namespace { | 26 | namespace { |
| 27 | 27 | ||
| 28 | constexpr size_t MaxOpenFiles = 512; | ||
| 29 | |||
| 28 | constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) { | 30 | constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) { |
| 29 | switch (mode) { | 31 | switch (mode) { |
| 30 | case Mode::Read: | 32 | case Mode::Read: |
| @@ -73,28 +75,30 @@ VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const { | |||
| 73 | VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { | 75 | VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { |
| 74 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 76 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 75 | 77 | ||
| 76 | if (const auto weak_iter = cache.find(path); weak_iter != cache.cend()) { | 78 | if (auto it = cache.find(path); it != cache.end()) { |
| 77 | const auto& weak = weak_iter->second; | 79 | if (auto file = it->second.lock(); file) { |
| 78 | 80 | return file; | |
| 79 | if (!weak.expired()) { | ||
| 80 | return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, weak.lock(), path, perms)); | ||
| 81 | } | 81 | } |
| 82 | } | 82 | } |
| 83 | 83 | ||
| 84 | auto backing = FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile); | 84 | if (!FS::Exists(path) || !FS::IsFile(path)) { |
| 85 | |||
| 86 | if (!backing) { | ||
| 87 | return nullptr; | 85 | return nullptr; |
| 88 | } | 86 | } |
| 89 | 87 | ||
| 90 | cache.insert_or_assign(path, std::move(backing)); | 88 | auto reference = std::make_unique<FileReference>(); |
| 89 | this->InsertReferenceIntoList(*reference); | ||
| 91 | 90 | ||
| 92 | // Cannot use make_shared as RealVfsFile constructor is private | 91 | auto file = |
| 93 | return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, backing, path, perms)); | 92 | std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, std::move(reference), path, perms)); |
| 93 | cache[path] = file; | ||
| 94 | |||
| 95 | return file; | ||
| 94 | } | 96 | } |
| 95 | 97 | ||
| 96 | VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { | 98 | VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { |
| 97 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 99 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 100 | cache.erase(path); | ||
| 101 | |||
| 98 | // Current usages of CreateFile expect to delete the contents of an existing file. | 102 | // Current usages of CreateFile expect to delete the contents of an existing file. |
| 99 | if (FS::IsFile(path)) { | 103 | if (FS::IsFile(path)) { |
| 100 | FS::IOFile temp{path, FS::FileAccessMode::Write, FS::FileType::BinaryFile}; | 104 | FS::IOFile temp{path, FS::FileAccessMode::Write, FS::FileType::BinaryFile}; |
| @@ -123,51 +127,22 @@ VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_ | |||
| 123 | VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) { | 127 | VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) { |
| 124 | const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); | 128 | const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); |
| 125 | const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); | 129 | const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); |
| 126 | const auto cached_file_iter = cache.find(old_path); | 130 | cache.erase(old_path); |
| 127 | 131 | cache.erase(new_path); | |
| 128 | if (cached_file_iter != cache.cend()) { | 132 | if (!FS::RenameFile(old_path, new_path)) { |
| 129 | auto file = cached_file_iter->second.lock(); | ||
| 130 | |||
| 131 | if (!cached_file_iter->second.expired()) { | ||
| 132 | file->Close(); | ||
| 133 | } | ||
| 134 | |||
| 135 | if (!FS::RenameFile(old_path, new_path)) { | ||
| 136 | return nullptr; | ||
| 137 | } | ||
| 138 | |||
| 139 | cache.erase(old_path); | ||
| 140 | file->Open(new_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile); | ||
| 141 | if (file->IsOpen()) { | ||
| 142 | cache.insert_or_assign(new_path, std::move(file)); | ||
| 143 | } else { | ||
| 144 | LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", new_path); | ||
| 145 | } | ||
| 146 | } else { | ||
| 147 | ASSERT(false); | ||
| 148 | return nullptr; | 133 | return nullptr; |
| 149 | } | 134 | } |
| 150 | |||
| 151 | return OpenFile(new_path, Mode::ReadWrite); | 135 | return OpenFile(new_path, Mode::ReadWrite); |
| 152 | } | 136 | } |
| 153 | 137 | ||
| 154 | bool RealVfsFilesystem::DeleteFile(std::string_view path_) { | 138 | bool RealVfsFilesystem::DeleteFile(std::string_view path_) { |
| 155 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 139 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 156 | const auto cached_iter = cache.find(path); | 140 | cache.erase(path); |
| 157 | |||
| 158 | if (cached_iter != cache.cend()) { | ||
| 159 | if (!cached_iter->second.expired()) { | ||
| 160 | cached_iter->second.lock()->Close(); | ||
| 161 | } | ||
| 162 | cache.erase(path); | ||
| 163 | } | ||
| 164 | |||
| 165 | return FS::RemoveFile(path); | 141 | return FS::RemoveFile(path); |
| 166 | } | 142 | } |
| 167 | 143 | ||
| 168 | VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) { | 144 | VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) { |
| 169 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 145 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 170 | // Cannot use make_shared as RealVfsDirectory constructor is private | ||
| 171 | return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); | 146 | return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); |
| 172 | } | 147 | } |
| 173 | 148 | ||
| @@ -176,7 +151,6 @@ VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms | |||
| 176 | if (!FS::CreateDirs(path)) { | 151 | if (!FS::CreateDirs(path)) { |
| 177 | return nullptr; | 152 | return nullptr; |
| 178 | } | 153 | } |
| 179 | // Cannot use make_shared as RealVfsDirectory constructor is private | ||
| 180 | return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); | 154 | return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); |
| 181 | } | 155 | } |
| 182 | 156 | ||
| @@ -194,73 +168,102 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_, | |||
| 194 | if (!FS::RenameDir(old_path, new_path)) { | 168 | if (!FS::RenameDir(old_path, new_path)) { |
| 195 | return nullptr; | 169 | return nullptr; |
| 196 | } | 170 | } |
| 171 | return OpenDirectory(new_path, Mode::ReadWrite); | ||
| 172 | } | ||
| 197 | 173 | ||
| 198 | for (auto& kv : cache) { | 174 | bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) { |
| 199 | // If the path in the cache doesn't start with old_path, then bail on this file. | 175 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 200 | if (kv.first.rfind(old_path, 0) != 0) { | 176 | return FS::RemoveDirRecursively(path); |
| 201 | continue; | 177 | } |
| 202 | } | ||
| 203 | 178 | ||
| 204 | const auto file_old_path = | 179 | void RealVfsFilesystem::RefreshReference(const std::string& path, Mode perms, |
| 205 | FS::SanitizePath(kv.first, FS::DirectorySeparator::PlatformDefault); | 180 | FileReference& reference) { |
| 206 | auto file_new_path = FS::SanitizePath(new_path + '/' + kv.first.substr(old_path.size()), | 181 | // Temporarily remove from list. |
| 207 | FS::DirectorySeparator::PlatformDefault); | 182 | this->RemoveReferenceFromList(reference); |
| 208 | const auto& cached = cache[file_old_path]; | ||
| 209 | 183 | ||
| 210 | if (cached.expired()) { | 184 | // Restore file if needed. |
| 211 | continue; | 185 | if (!reference.file) { |
| 212 | } | 186 | this->EvictSingleReference(); |
| 213 | 187 | ||
| 214 | auto file = cached.lock(); | 188 | reference.file = |
| 215 | cache.erase(file_old_path); | 189 | FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile); |
| 216 | file->Open(file_new_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile); | 190 | if (reference.file) { |
| 217 | if (file->IsOpen()) { | 191 | num_open_files++; |
| 218 | cache.insert_or_assign(std::move(file_new_path), std::move(file)); | ||
| 219 | } else { | ||
| 220 | LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", file_new_path); | ||
| 221 | } | 192 | } |
| 222 | } | 193 | } |
| 223 | 194 | ||
| 224 | return OpenDirectory(new_path, Mode::ReadWrite); | 195 | // Reinsert into list. |
| 196 | this->InsertReferenceIntoList(reference); | ||
| 225 | } | 197 | } |
| 226 | 198 | ||
| 227 | bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) { | 199 | void RealVfsFilesystem::DropReference(std::unique_ptr<FileReference>&& reference) { |
| 228 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 200 | // Remove from list. |
| 201 | this->RemoveReferenceFromList(*reference); | ||
| 229 | 202 | ||
| 230 | for (auto& kv : cache) { | 203 | // Close the file. |
| 231 | // If the path in the cache doesn't start with path, then bail on this file. | 204 | if (reference->file) { |
| 232 | if (kv.first.rfind(path, 0) != 0) { | 205 | reference->file.reset(); |
| 233 | continue; | 206 | num_open_files--; |
| 234 | } | 207 | } |
| 208 | } | ||
| 235 | 209 | ||
| 236 | const auto& entry = cache[kv.first]; | 210 | void RealVfsFilesystem::EvictSingleReference() { |
| 237 | if (!entry.expired()) { | 211 | if (num_open_files < MaxOpenFiles || open_references.empty()) { |
| 238 | entry.lock()->Close(); | 212 | return; |
| 239 | } | 213 | } |
| 214 | |||
| 215 | // Get and remove from list. | ||
| 216 | auto& reference = open_references.back(); | ||
| 217 | this->RemoveReferenceFromList(reference); | ||
| 240 | 218 | ||
| 241 | cache.erase(kv.first); | 219 | // Close the file. |
| 220 | if (reference.file) { | ||
| 221 | reference.file.reset(); | ||
| 222 | num_open_files--; | ||
| 242 | } | 223 | } |
| 243 | 224 | ||
| 244 | return FS::RemoveDirRecursively(path); | 225 | // Reinsert into closed list. |
| 226 | this->InsertReferenceIntoList(reference); | ||
| 227 | } | ||
| 228 | |||
| 229 | void RealVfsFilesystem::InsertReferenceIntoList(FileReference& reference) { | ||
| 230 | if (reference.file) { | ||
| 231 | open_references.push_front(reference); | ||
| 232 | } else { | ||
| 233 | closed_references.push_front(reference); | ||
| 234 | } | ||
| 235 | } | ||
| 236 | |||
| 237 | void RealVfsFilesystem::RemoveReferenceFromList(FileReference& reference) { | ||
| 238 | if (reference.file) { | ||
| 239 | open_references.erase(open_references.iterator_to(reference)); | ||
| 240 | } else { | ||
| 241 | closed_references.erase(closed_references.iterator_to(reference)); | ||
| 242 | } | ||
| 245 | } | 243 | } |
| 246 | 244 | ||
| 247 | RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FS::IOFile> backing_, | 245 | RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr<FileReference> reference_, |
| 248 | const std::string& path_, Mode perms_) | 246 | const std::string& path_, Mode perms_) |
| 249 | : base(base_), backing(std::move(backing_)), path(path_), parent_path(FS::GetParentPath(path_)), | 247 | : base(base_), reference(std::move(reference_)), path(path_), |
| 250 | path_components(FS::SplitPathComponents(path_)), perms(perms_) {} | 248 | parent_path(FS::GetParentPath(path_)), path_components(FS::SplitPathComponents(path_)), |
| 249 | perms(perms_) {} | ||
| 251 | 250 | ||
| 252 | RealVfsFile::~RealVfsFile() = default; | 251 | RealVfsFile::~RealVfsFile() { |
| 252 | base.DropReference(std::move(reference)); | ||
| 253 | } | ||
| 253 | 254 | ||
| 254 | std::string RealVfsFile::GetName() const { | 255 | std::string RealVfsFile::GetName() const { |
| 255 | return path_components.back(); | 256 | return path_components.back(); |
| 256 | } | 257 | } |
| 257 | 258 | ||
| 258 | std::size_t RealVfsFile::GetSize() const { | 259 | std::size_t RealVfsFile::GetSize() const { |
| 259 | return backing->GetSize(); | 260 | base.RefreshReference(path, perms, *reference); |
| 261 | return reference->file ? reference->file->GetSize() : 0; | ||
| 260 | } | 262 | } |
| 261 | 263 | ||
| 262 | bool RealVfsFile::Resize(std::size_t new_size) { | 264 | bool RealVfsFile::Resize(std::size_t new_size) { |
| 263 | return backing->SetSize(new_size); | 265 | base.RefreshReference(path, perms, *reference); |
| 266 | return reference->file ? reference->file->SetSize(new_size) : false; | ||
| 264 | } | 267 | } |
| 265 | 268 | ||
| 266 | VirtualDir RealVfsFile::GetContainingDirectory() const { | 269 | VirtualDir RealVfsFile::GetContainingDirectory() const { |
| @@ -276,27 +279,25 @@ bool RealVfsFile::IsReadable() const { | |||
| 276 | } | 279 | } |
| 277 | 280 | ||
| 278 | std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { | 281 | std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { |
| 279 | if (!backing->Seek(static_cast<s64>(offset))) { | 282 | base.RefreshReference(path, perms, *reference); |
| 283 | if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) { | ||
| 280 | return 0; | 284 | return 0; |
| 281 | } | 285 | } |
| 282 | return backing->ReadSpan(std::span{data, length}); | 286 | return reference->file->ReadSpan(std::span{data, length}); |
| 283 | } | 287 | } |
| 284 | 288 | ||
| 285 | std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { | 289 | std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { |
| 286 | if (!backing->Seek(static_cast<s64>(offset))) { | 290 | base.RefreshReference(path, perms, *reference); |
| 291 | if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) { | ||
| 287 | return 0; | 292 | return 0; |
| 288 | } | 293 | } |
| 289 | return backing->WriteSpan(std::span{data, length}); | 294 | return reference->file->WriteSpan(std::span{data, length}); |
| 290 | } | 295 | } |
| 291 | 296 | ||
| 292 | bool RealVfsFile::Rename(std::string_view name) { | 297 | bool RealVfsFile::Rename(std::string_view name) { |
| 293 | return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr; | 298 | return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr; |
| 294 | } | 299 | } |
| 295 | 300 | ||
| 296 | void RealVfsFile::Close() { | ||
| 297 | backing->Close(); | ||
| 298 | } | ||
| 299 | |||
| 300 | // TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if | 301 | // TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if |
| 301 | // constexpr' because there is a compile error in the branch not used. | 302 | // constexpr' because there is a compile error in the branch not used. |
| 302 | 303 | ||
diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h index b92c84316..d8c900e33 100644 --- a/src/core/file_sys/vfs_real.h +++ b/src/core/file_sys/vfs_real.h | |||
| @@ -3,8 +3,9 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <map> | ||
| 6 | #include <string_view> | 7 | #include <string_view> |
| 7 | #include <boost/container/flat_map.hpp> | 8 | #include "common/intrusive_list.h" |
| 8 | #include "core/file_sys/mode.h" | 9 | #include "core/file_sys/mode.h" |
| 9 | #include "core/file_sys/vfs.h" | 10 | #include "core/file_sys/vfs.h" |
| 10 | 11 | ||
| @@ -14,6 +15,11 @@ class IOFile; | |||
| 14 | 15 | ||
| 15 | namespace FileSys { | 16 | namespace FileSys { |
| 16 | 17 | ||
| 18 | struct FileReference : public Common::IntrusiveListBaseNode<FileReference> { | ||
| 19 | std::shared_ptr<Common::FS::IOFile> file{}; | ||
| 20 | }; | ||
| 21 | |||
| 22 | class RealVfsFile; | ||
| 17 | class RealVfsFilesystem : public VfsFilesystem { | 23 | class RealVfsFilesystem : public VfsFilesystem { |
| 18 | public: | 24 | public: |
| 19 | RealVfsFilesystem(); | 25 | RealVfsFilesystem(); |
| @@ -35,7 +41,21 @@ public: | |||
| 35 | bool DeleteDirectory(std::string_view path) override; | 41 | bool DeleteDirectory(std::string_view path) override; |
| 36 | 42 | ||
| 37 | private: | 43 | private: |
| 38 | boost::container::flat_map<std::string, std::weak_ptr<Common::FS::IOFile>> cache; | 44 | using ReferenceListType = Common::IntrusiveListBaseTraits<FileReference>::ListType; |
| 45 | std::map<std::string, std::weak_ptr<VfsFile>, std::less<>> cache; | ||
| 46 | ReferenceListType open_references; | ||
| 47 | ReferenceListType closed_references; | ||
| 48 | size_t num_open_files{}; | ||
| 49 | |||
| 50 | private: | ||
| 51 | friend class RealVfsFile; | ||
| 52 | void RefreshReference(const std::string& path, Mode perms, FileReference& reference); | ||
| 53 | void DropReference(std::unique_ptr<FileReference>&& reference); | ||
| 54 | void EvictSingleReference(); | ||
| 55 | |||
| 56 | private: | ||
| 57 | void InsertReferenceIntoList(FileReference& reference); | ||
| 58 | void RemoveReferenceFromList(FileReference& reference); | ||
| 39 | }; | 59 | }; |
| 40 | 60 | ||
| 41 | // An implementation of VfsFile that represents a file on the user's computer. | 61 | // An implementation of VfsFile that represents a file on the user's computer. |
| @@ -57,13 +77,11 @@ public: | |||
| 57 | bool Rename(std::string_view name) override; | 77 | bool Rename(std::string_view name) override; |
| 58 | 78 | ||
| 59 | private: | 79 | private: |
| 60 | RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<Common::FS::IOFile> backing, | 80 | RealVfsFile(RealVfsFilesystem& base, std::unique_ptr<FileReference> reference, |
| 61 | const std::string& path, Mode perms = Mode::Read); | 81 | const std::string& path, Mode perms = Mode::Read); |
| 62 | 82 | ||
| 63 | void Close(); | ||
| 64 | |||
| 65 | RealVfsFilesystem& base; | 83 | RealVfsFilesystem& base; |
| 66 | std::shared_ptr<Common::FS::IOFile> backing; | 84 | std::unique_ptr<FileReference> reference; |
| 67 | std::string path; | 85 | std::string path; |
| 68 | std::string parent_path; | 86 | std::string parent_path; |
| 69 | std::vector<std::string> path_components; | 87 | std::vector<std::string> path_components; |