| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "flutter/fml/file.h" |
| |
| #include <Fileapi.h> |
| #include <Shlwapi.h> |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <sys/stat.h> |
| #include <string> |
| |
| #include <algorithm> |
| #include <climits> |
| #include <cstring> |
| #include <optional> |
| #include <sstream> |
| |
| #include "flutter/fml/build_config.h" |
| #include "flutter/fml/mapping.h" |
| #include "flutter/fml/platform/win/errors_win.h" |
| #include "flutter/fml/platform/win/wstring_conversion.h" |
| |
| namespace fml { |
| |
| static std::string GetFullHandlePath(const fml::UniqueFD& handle) { |
| wchar_t buffer[MAX_PATH] = {0}; |
| const DWORD buffer_size = ::GetFinalPathNameByHandle( |
| handle.get(), buffer, MAX_PATH, FILE_NAME_NORMALIZED); |
| if (buffer_size == 0) { |
| return {}; |
| } |
| return WideStringToUtf8({buffer, buffer_size}); |
| } |
| |
| static bool IsAbsolutePath(const char* path) { |
| if (path == nullptr || strlen(path) == 0) { |
| return false; |
| } |
| |
| auto wpath = Utf8ToWideString({path}); |
| if (wpath.empty()) { |
| return false; |
| } |
| |
| return ::PathIsRelative(wpath.c_str()) == FALSE; |
| } |
| |
| static std::string GetAbsolutePath(const fml::UniqueFD& base_directory, |
| const char* subpath) { |
| std::string path; |
| if (IsAbsolutePath(subpath)) { |
| path = subpath; |
| } else { |
| std::stringstream stream; |
| stream << GetFullHandlePath(base_directory) << "\\" << subpath; |
| path = stream.str(); |
| } |
| std::replace(path.begin(), path.end(), '/', '\\'); |
| return path; |
| } |
| |
| static std::wstring GetTemporaryDirectoryPath() { |
| wchar_t wchar_path[MAX_PATH]; |
| auto result_size = ::GetTempPath(MAX_PATH, wchar_path); |
| if (result_size > 0) { |
| return {wchar_path, result_size}; |
| } |
| return {}; |
| } |
| |
| static DWORD GetDesiredAccessFlags(FilePermission permission) { |
| switch (permission) { |
| case FilePermission::kRead: |
| return GENERIC_READ; |
| case FilePermission::kWrite: |
| return GENERIC_WRITE; |
| case FilePermission::kReadWrite: |
| return GENERIC_READ | GENERIC_WRITE; |
| } |
| return GENERIC_READ; |
| } |
| |
| static DWORD GetShareFlags(FilePermission permission) { |
| switch (permission) { |
| case FilePermission::kRead: |
| return FILE_SHARE_READ; |
| case FilePermission::kWrite: |
| return FILE_SHARE_WRITE; |
| case FilePermission::kReadWrite: |
| return FILE_SHARE_READ | FILE_SHARE_WRITE; |
| } |
| return FILE_SHARE_READ; |
| } |
| |
| static DWORD GetFileAttributesForUtf8Path(const char* absolute_path) { |
| return ::GetFileAttributes(Utf8ToWideString(absolute_path).c_str()); |
| } |
| |
| static DWORD GetFileAttributesForUtf8Path(const fml::UniqueFD& base_directory, |
| const char* path) { |
| std::string full_path = GetAbsolutePath(base_directory, path); |
| return GetFileAttributesForUtf8Path(full_path.c_str()); |
| } |
| |
| std::string CreateTemporaryDirectory() { |
| // Get the system temporary directory. |
| auto temp_dir_container = GetTemporaryDirectoryPath(); |
| if (temp_dir_container.empty()) { |
| FML_DLOG(ERROR) << "Could not get system temporary directory."; |
| return {}; |
| } |
| |
| // Create a UUID. |
| UUID uuid; |
| RPC_STATUS status = UuidCreateSequential(&uuid); |
| if (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY) { |
| FML_DLOG(ERROR) << "Could not create UUID for temporary directory."; |
| return {}; |
| } |
| |
| RPC_WSTR uuid_string; |
| status = UuidToString(&uuid, &uuid_string); |
| if (status != RPC_S_OK) { |
| FML_DLOG(ERROR) << "Could not map UUID to string for temporary directory."; |
| return {}; |
| } |
| |
| std::wstring uuid_str(reinterpret_cast<wchar_t*>(uuid_string)); |
| RpcStringFree(&uuid_string); |
| |
| // Join the two and create a path to the new temporary directory. |
| |
| std::wstringstream stream; |
| stream << temp_dir_container << "\\" << uuid_str; |
| auto temp_dir = stream.str(); |
| |
| auto dir_fd = OpenDirectory(WideStringToUtf8(temp_dir).c_str(), true, |
| FilePermission::kReadWrite); |
| if (!dir_fd.is_valid()) { |
| FML_DLOG(ERROR) << "Could not get temporary directory file descriptor. " |
| << GetLastErrorMessage(); |
| return {}; |
| } |
| |
| return WideStringToUtf8(std::move(temp_dir)); |
| } |
| |
| fml::UniqueFD OpenFile(const fml::UniqueFD& base_directory, |
| const char* path, |
| bool create_if_necessary, |
| FilePermission permission) { |
| return OpenFile(GetAbsolutePath(base_directory, path).c_str(), |
| create_if_necessary, permission); |
| } |
| |
| fml::UniqueFD OpenFile(const char* path, |
| bool create_if_necessary, |
| FilePermission permission) { |
| if (path == nullptr || strlen(path) == 0) { |
| return {}; |
| } |
| |
| auto file_name = Utf8ToWideString({path}); |
| |
| if (file_name.empty()) { |
| return {}; |
| } |
| |
| const DWORD creation_disposition = |
| create_if_necessary ? OPEN_ALWAYS : OPEN_EXISTING; |
| |
| const DWORD flags = FILE_ATTRIBUTE_NORMAL; |
| |
| auto handle = |
| CreateFile(file_name.c_str(), // lpFileName |
| GetDesiredAccessFlags(permission), // dwDesiredAccess |
| GetShareFlags(permission), // dwShareMode |
| nullptr, // lpSecurityAttributes // |
| creation_disposition, // dwCreationDisposition // |
| flags, // dwFlagsAndAttributes // |
| nullptr // hTemplateFile // |
| ); |
| |
| if (handle == INVALID_HANDLE_VALUE) { |
| return {}; |
| } |
| |
| return fml::UniqueFD{handle}; |
| } |
| |
| fml::UniqueFD OpenDirectory(const fml::UniqueFD& base_directory, |
| const char* path, |
| bool create_if_necessary, |
| FilePermission permission) { |
| return OpenDirectory(GetAbsolutePath(base_directory, path).c_str(), |
| create_if_necessary, permission); |
| } |
| |
| fml::UniqueFD OpenDirectory(const char* path, |
| bool create_if_necessary, |
| FilePermission permission) { |
| if (path == nullptr || strlen(path) == 0) { |
| return {}; |
| } |
| |
| auto file_name = Utf8ToWideString({path}); |
| |
| if (file_name.empty()) { |
| return {}; |
| } |
| |
| if (create_if_necessary) { |
| if (!::CreateDirectory(file_name.c_str(), nullptr)) { |
| if (GetLastError() != ERROR_ALREADY_EXISTS) { |
| FML_DLOG(ERROR) << "Could not create directory. " |
| << GetLastErrorMessage(); |
| return {}; |
| } |
| } |
| } |
| |
| const DWORD creation_disposition = OPEN_EXISTING; |
| |
| const DWORD flags = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS; |
| |
| auto handle = |
| CreateFile(file_name.c_str(), // lpFileName |
| GetDesiredAccessFlags(permission), // dwDesiredAccess |
| GetShareFlags(permission), // dwShareMode |
| nullptr, // lpSecurityAttributes // |
| creation_disposition, // dwCreationDisposition // |
| flags, // dwFlagsAndAttributes // |
| nullptr // hTemplateFile // |
| ); |
| |
| if (handle == INVALID_HANDLE_VALUE) { |
| return {}; |
| } |
| |
| return fml::UniqueFD{handle}; |
| } |
| |
| fml::UniqueFD Duplicate(fml::UniqueFD::element_type descriptor) { |
| if (descriptor == INVALID_HANDLE_VALUE) { |
| return fml::UniqueFD{}; |
| } |
| |
| HANDLE duplicated = INVALID_HANDLE_VALUE; |
| |
| if (!::DuplicateHandle( |
| GetCurrentProcess(), // source process |
| descriptor, // source handle |
| GetCurrentProcess(), // target process |
| &duplicated, // target handle |
| 0, // desired access (ignored because DUPLICATE_SAME_ACCESS) |
| FALSE, // inheritable |
| DUPLICATE_SAME_ACCESS) // options |
| ) { |
| return fml::UniqueFD{}; |
| } |
| |
| return fml::UniqueFD{duplicated}; |
| } |
| |
| bool IsDirectory(const fml::UniqueFD& directory) { |
| BY_HANDLE_FILE_INFORMATION info; |
| if (!::GetFileInformationByHandle(directory.get(), &info)) { |
| return false; |
| } |
| return info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; |
| } |
| |
| bool IsDirectory(const fml::UniqueFD& base_directory, const char* path) { |
| return GetFileAttributesForUtf8Path(base_directory, path) & |
| FILE_ATTRIBUTE_DIRECTORY; |
| } |
| |
| bool IsFile(const std::string& path) { |
| DWORD attributes = GetFileAttributesForUtf8Path(path.c_str()); |
| if (attributes == INVALID_FILE_ATTRIBUTES) { |
| return false; |
| } |
| return !(attributes & |
| (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)); |
| } |
| |
| bool UnlinkDirectory(const char* path) { |
| if (!::RemoveDirectory(Utf8ToWideString(path).c_str())) { |
| FML_DLOG(ERROR) << "Could not remove directory: '" << path << "'. " |
| << GetLastErrorMessage(); |
| return false; |
| } |
| return true; |
| } |
| |
| bool UnlinkDirectory(const fml::UniqueFD& base_directory, const char* path) { |
| if (!::RemoveDirectory( |
| Utf8ToWideString(GetAbsolutePath(base_directory, path)).c_str())) { |
| FML_DLOG(ERROR) << "Could not remove directory: '" << path << "'. " |
| << GetLastErrorMessage(); |
| return false; |
| } |
| return true; |
| } |
| |
| bool UnlinkFile(const char* path) { |
| if (!::DeleteFile(Utf8ToWideString(path).c_str())) { |
| FML_DLOG(ERROR) << "Could not remove file: '" << path << "'. " |
| << GetLastErrorMessage(); |
| return false; |
| } |
| return true; |
| } |
| |
| bool UnlinkFile(const fml::UniqueFD& base_directory, const char* path) { |
| if (!::DeleteFile( |
| Utf8ToWideString(GetAbsolutePath(base_directory, path)).c_str())) { |
| FML_DLOG(ERROR) << "Could not remove file: '" << path << "'. " |
| << GetLastErrorMessage(); |
| return false; |
| } |
| return true; |
| } |
| |
| bool TruncateFile(const fml::UniqueFD& file, size_t size) { |
| LARGE_INTEGER large_size; |
| large_size.QuadPart = size; |
| large_size.LowPart = SetFilePointer(file.get(), large_size.LowPart, |
| &large_size.HighPart, FILE_BEGIN); |
| if (large_size.LowPart == INVALID_SET_FILE_POINTER && |
| GetLastError() != NO_ERROR) { |
| FML_DLOG(ERROR) << "Could not update file size. " << GetLastErrorMessage(); |
| return false; |
| } |
| |
| if (!::SetEndOfFile(file.get())) { |
| FML_DLOG(ERROR) << "Could not commit file size update. " |
| << GetLastErrorMessage(); |
| return false; |
| } |
| return true; |
| } |
| |
| bool FileExists(const fml::UniqueFD& base_directory, const char* path) { |
| return GetFileAttributesForUtf8Path(base_directory, path) != |
| INVALID_FILE_ATTRIBUTES; |
| } |
| |
| // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/102838 this is |
| // not atomic on Windows. |
| bool WriteAtomically(const fml::UniqueFD& base_directory, |
| const char* file_name, |
| const Mapping& mapping) { |
| if (file_name == nullptr) { |
| return false; |
| } |
| |
| auto file_path = GetAbsolutePath(base_directory, file_name); |
| auto temp_file = |
| OpenFile(file_path.c_str(), true, FilePermission::kReadWrite); |
| |
| if (!temp_file.is_valid()) { |
| FML_DLOG(ERROR) << "Could not create file: " << file_path.c_str() << " " |
| << GetLastError() << " " << GetLastErrorMessage(); |
| return false; |
| } |
| |
| if (!TruncateFile(temp_file, mapping.GetSize())) { |
| FML_DLOG(ERROR) << "Could not truncate the file to the correct size. " |
| << GetLastErrorMessage(); |
| return false; |
| } |
| |
| { |
| FileMapping temp_file_mapping(temp_file, {FileMapping::Protection::kRead, |
| FileMapping::Protection::kWrite}); |
| if (temp_file_mapping.GetSize() != mapping.GetSize()) { |
| FML_DLOG(ERROR) << "Temporary file mapping size was incorrect. Is " |
| << temp_file_mapping.GetSize() << ". Should be " |
| << mapping.GetSize() << "."; |
| return false; |
| } |
| |
| if (temp_file_mapping.GetMutableMapping() == nullptr) { |
| FML_DLOG(ERROR) << "Temporary file does not have a mutable mapping."; |
| return false; |
| } |
| |
| ::memcpy(temp_file_mapping.GetMutableMapping(), mapping.GetMapping(), |
| mapping.GetSize()); |
| |
| if (!::FlushViewOfFile(temp_file_mapping.GetMutableMapping(), |
| mapping.GetSize())) { |
| FML_DLOG(ERROR) << "Could not flush file view. " << GetLastErrorMessage(); |
| return false; |
| } |
| |
| if (!::FlushFileBuffers(temp_file.get())) { |
| FML_DLOG(ERROR) << "Could not flush file buffers. " |
| << GetLastErrorMessage(); |
| return false; |
| } |
| |
| // File mapping is detroyed. |
| } |
| |
| temp_file.reset(); |
| |
| return true; |
| } |
| |
| bool VisitFiles(const fml::UniqueFD& directory, const FileVisitor& visitor) { |
| std::string search_pattern = GetFullHandlePath(directory) + "\\*"; |
| WIN32_FIND_DATA find_file_data; |
| HANDLE find_handle = ::FindFirstFile(Utf8ToWideString(search_pattern).c_str(), |
| &find_file_data); |
| |
| if (find_handle == INVALID_HANDLE_VALUE) { |
| FML_DLOG(ERROR) << "Can't open the directory. Error: " |
| << GetLastErrorMessage(); |
| return true; // continue to visit other files |
| } |
| |
| do { |
| std::string filename = WideStringToUtf8(find_file_data.cFileName); |
| if (filename != "." && filename != "..") { |
| if (!visitor(directory, filename)) { |
| ::FindClose(find_handle); |
| return false; |
| } |
| } |
| } while (::FindNextFile(find_handle, &find_file_data)); |
| ::FindClose(find_handle); |
| return true; |
| } |
| |
| } // namespace fml |