diff --git a/fml/platform/win/file_win.cc b/fml/platform/win/file_win.cc index 2815584ae4b82..7b0e785f40c0e 100644 --- a/fml/platform/win/file_win.cc +++ b/fml/platform/win/file_win.cc @@ -7,10 +7,14 @@ #include #include #include +#include +#include +#include #include #include #include +#include #include #include "flutter/fml/build_config.h" @@ -21,6 +25,34 @@ namespace fml { static std::string GetFullHandlePath(const fml::UniqueFD& handle) { + // Although the documentation claims that GetFinalPathNameByHandle is + // supported for UWP apps, turns out it returns ACCESS_DENIED in this case + // hence the need to workaround it by maintaining a map of file handles to + // absolute paths populated by fml::OpenDirectory. +#ifdef WINUWP + std::optional found = + fml::internal::os_win::UniqueFDTraits::GetCacheEntry(handle.get()); + + if (found) { + FILE_ID_INFO info; + + BOOL result = GetFileInformationByHandleEx( + handle.get(), FILE_INFO_BY_HANDLE_CLASS::FileIdInfo, &info, + sizeof(FILE_ID_INFO)); + + // Assuming it was possible to retrieve fileinfo, compare the id field. The + // handle hasn't been reused if the file identifier is the same as when it + // was cached + if (result && memcmp(found.value().id.Identifier, info.FileId.Identifier, + sizeof(FILE_ID_INFO))) { + return WideStringToString(found.value().filename); + } else { + fml::internal::os_win::UniqueFDTraits::RemoveCacheEntry(handle.get()); + } + } + + return std::string(); +#else wchar_t buffer[MAX_PATH] = {0}; const DWORD buffer_size = ::GetFinalPathNameByHandle( handle.get(), buffer, MAX_PATH, FILE_NAME_NORMALIZED); @@ -30,6 +62,7 @@ static std::string GetFullHandlePath(const fml::UniqueFD& handle) { return {}; } return WideStringToString({buffer, buffer_size}); +#endif } static std::string GetAbsolutePath(const fml::UniqueFD& base_directory, @@ -223,6 +256,22 @@ fml::UniqueFD OpenDirectory(const char* path, return {}; } +#ifdef WINUWP + FILE_ID_INFO info; + + BOOL result = GetFileInformationByHandleEx( + handle, FILE_INFO_BY_HANDLE_CLASS::FileIdInfo, &info, + sizeof(FILE_ID_INFO)); + + // Only cache if it is possible to get valid a fileinformation to extract the + // fileid to ensure correct handle versioning. + if (result) { + fml::internal::os_win::DirCacheEntry fc{file_name, info.FileId}; + + fml::internal::os_win::UniqueFDTraits::StoreCacheEntry(handle, fc); + } +#endif + return fml::UniqueFD{handle}; } diff --git a/fml/unique_fd.cc b/fml/unique_fd.cc index 2da6d938f9875..5721b0f82958d 100644 --- a/fml/unique_fd.cc +++ b/fml/unique_fd.cc @@ -13,7 +13,10 @@ namespace internal { namespace os_win { -void UniqueFDTraits::Free(HANDLE fd) { +std::mutex UniqueFDTraits::file_map_mutex; +std::map UniqueFDTraits::file_map; + +void UniqueFDTraits::Free_Handle(HANDLE fd) { CloseHandle(fd); } diff --git a/fml/unique_fd.h b/fml/unique_fd.h index 5b74073f426a4..c4b79dc3496e4 100644 --- a/fml/unique_fd.h +++ b/fml/unique_fd.h @@ -10,6 +10,9 @@ #if OS_WIN #include +#include +#include +#include #else // OS_WIN #include #include @@ -22,10 +25,46 @@ namespace internal { namespace os_win { +struct DirCacheEntry { + std::wstring filename; + FILE_ID_128 id; +}; + +// The order of these is important. Must come before UniqueFDTraits struct +// else linker error. Embedding in struct also causes linker error. + struct UniqueFDTraits { + static std::mutex file_map_mutex; + static std::map file_map; + static HANDLE InvalidValue() { return INVALID_HANDLE_VALUE; } static bool IsValid(HANDLE value) { return value != InvalidValue(); } - static void Free(HANDLE fd); + static void Free_Handle(HANDLE fd); + + static void Free(HANDLE fd) { + RemoveCacheEntry(fd); + + UniqueFDTraits::Free_Handle(fd); + } + + static void RemoveCacheEntry(HANDLE fd) { + const std::lock_guard lock(file_map_mutex); + + file_map.erase(fd); + } + + static void StoreCacheEntry(HANDLE fd, DirCacheEntry state) { + const std::lock_guard lock(file_map_mutex); + file_map[fd] = state; + } + + static std::optional GetCacheEntry(HANDLE fd) { + const std::lock_guard lock(file_map_mutex); + auto found = file_map.find(fd); + return found == file_map.end() + ? std::nullopt + : std::optional{found->second}; + } }; } // namespace os_win