1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "flutter/fml/file.h"
6
7 // ACE PC preivew.
8 #ifdef WINDOWS_PLATFORM
9 #include <fileapi.h>
10 #include <shlwapi.h>
11 #else
12 #include <Fileapi.h>
13 #include <Shlwapi.h>
14 #endif
15
16 #include <fcntl.h>
17 #include <limits.h>
18 #include <sys/stat.h>
19
20 #include <algorithm>
21 #include <sstream>
22
23 #include "flutter/fml/build_config.h"
24 #include "flutter/fml/mapping.h"
25 #include "flutter/fml/platform/win/errors_win.h"
26 #include "flutter/fml/platform/win/wstring_conversion.h"
27
28 #if defined(OS_WIN)
29 #define S_ISREG(m) (((m)&S_IFMT) == S_IFREG)
30 #endif
31
32 namespace fml {
33
GetFullHandlePath(const fml::UniqueFD & handle)34 static std::string GetFullHandlePath(const fml::UniqueFD& handle) {
35 wchar_t buffer[MAX_PATH] = {0};
36 const DWORD buffer_size = ::GetFinalPathNameByHandle(
37 handle.get(), buffer, MAX_PATH, FILE_NAME_NORMALIZED);
38 if (buffer_size == 0) {
39 FML_DLOG(ERROR) << "Could not get file handle path. "
40 << GetLastErrorMessage();
41 return {};
42 }
43 return WideStringToString({buffer, buffer_size});
44 }
45
GetAbsolutePath(const fml::UniqueFD & base_directory,const char * subpath)46 static std::string GetAbsolutePath(const fml::UniqueFD& base_directory,
47 const char* subpath) {
48 std::stringstream stream;
49 stream << GetFullHandlePath(base_directory) << "\\" << subpath;
50 auto path = stream.str();
51 std::replace(path.begin(), path.end(), '/', '\\');
52 return path;
53 }
54
GetTemporaryDirectoryPath()55 static std::wstring GetTemporaryDirectoryPath() {
56 wchar_t wchar_path[MAX_PATH];
57 auto result_size = ::GetTempPath(MAX_PATH, wchar_path);
58 if (result_size > 0) {
59 return {wchar_path, result_size};
60 }
61 FML_DLOG(ERROR) << "Could not get temporary directory path. "
62 << GetLastErrorMessage();
63 return {};
64 }
65
GetDesiredAccessFlags(FilePermission permission)66 static DWORD GetDesiredAccessFlags(FilePermission permission) {
67 switch (permission) {
68 case FilePermission::kRead:
69 return GENERIC_READ;
70 case FilePermission::kWrite:
71 return GENERIC_WRITE;
72 case FilePermission::kReadWrite:
73 return GENERIC_READ | GENERIC_WRITE;
74 }
75 return GENERIC_READ;
76 }
77
GetShareFlags(FilePermission permission)78 static DWORD GetShareFlags(FilePermission permission) {
79 switch (permission) {
80 case FilePermission::kRead:
81 return FILE_SHARE_READ;
82 case FilePermission::kWrite:
83 return FILE_SHARE_WRITE;
84 case FilePermission::kReadWrite:
85 return FILE_SHARE_READ | FILE_SHARE_WRITE;
86 }
87 return FILE_SHARE_READ;
88 }
89
CreateTemporaryDirectory()90 std::string CreateTemporaryDirectory() {
91 // Get the system temporary directory.
92 auto temp_dir_container = GetTemporaryDirectoryPath();
93 if (temp_dir_container.size() == 0) {
94 FML_DLOG(ERROR) << "Could not get system temporary directory.";
95 return {};
96 }
97
98 // Create a UUID.
99 UUID uuid;
100 RPC_STATUS status = UuidCreateSequential(&uuid);
101 if (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY) {
102 FML_DLOG(ERROR) << "Could not create UUID";
103 return {};
104 }
105
106 RPC_WSTR uuid_string;
107 status = UuidToString(&uuid, &uuid_string);
108 if (status != RPC_S_OK) {
109 FML_DLOG(ERROR) << "Could not create UUID to string.";
110 return {};
111 }
112
113 std::wstring uuid_str(reinterpret_cast<wchar_t*>(uuid_string));
114 RpcStringFree(&uuid_string);
115
116 // Join the two and create a path to the new temporary directory.
117
118 std::wstringstream stream;
119 stream << temp_dir_container << "\\" << uuid_str;
120 auto temp_dir = stream.str();
121
122 auto dir_fd = OpenDirectory(WideStringToString(temp_dir).c_str(), true,
123 FilePermission::kReadWrite);
124 if (!dir_fd.is_valid()) {
125 FML_DLOG(ERROR) << "Could not get temporary directory FD. "
126 << GetLastErrorMessage();
127 return {};
128 }
129
130 return WideStringToString(std::move(temp_dir));
131 }
132
OpenFile(const fml::UniqueFD & base_directory,const char * path,bool create_if_necessary,FilePermission permission)133 fml::UniqueFD OpenFile(const fml::UniqueFD& base_directory,
134 const char* path,
135 bool create_if_necessary,
136 FilePermission permission) {
137 return OpenFile(GetAbsolutePath(base_directory, path).c_str(),
138 create_if_necessary, permission);
139 }
140
OpenFile(const char * path,bool create_if_necessary,FilePermission permission)141 fml::UniqueFD OpenFile(const char* path,
142 bool create_if_necessary,
143 FilePermission permission) {
144 if (path == nullptr || strlen(path) == 0) {
145 return {};
146 }
147
148 auto file_name = StringToWideString({path});
149
150 if (file_name.size() == 0) {
151 return {};
152 }
153
154 const DWORD creation_disposition =
155 create_if_necessary ? CREATE_NEW : OPEN_EXISTING;
156
157 const DWORD flags = FILE_ATTRIBUTE_NORMAL;
158
159 auto handle =
160 CreateFile(file_name.c_str(), // lpFileName
161 GetDesiredAccessFlags(permission), // dwDesiredAccess
162 GetShareFlags(permission), // dwShareMode
163 nullptr, // lpSecurityAttributes //
164 creation_disposition, // dwCreationDisposition //
165 flags, // dwFlagsAndAttributes //
166 nullptr // hTemplateFile //
167 );
168
169 if (handle == INVALID_HANDLE_VALUE) {
170 FML_DLOG(ERROR) << "Could not open file. " << GetLastErrorMessage();
171 return {};
172 }
173
174 return fml::UniqueFD{handle};
175 }
176
OpenDirectory(const fml::UniqueFD & base_directory,const char * path,bool create_if_necessary,FilePermission permission)177 fml::UniqueFD OpenDirectory(const fml::UniqueFD& base_directory,
178 const char* path,
179 bool create_if_necessary,
180 FilePermission permission) {
181 return OpenDirectory(GetAbsolutePath(base_directory, path).c_str(),
182 create_if_necessary, permission);
183 }
184
OpenDirectory(const char * path,bool create_if_necessary,FilePermission permission)185 fml::UniqueFD OpenDirectory(const char* path,
186 bool create_if_necessary,
187 FilePermission permission) {
188 if (path == nullptr || strlen(path) == 0) {
189 return {};
190 }
191
192 auto file_name = StringToWideString({path});
193
194 if (file_name.size() == 0) {
195 return {};
196 }
197
198 if (create_if_necessary) {
199 if (!::CreateDirectory(file_name.c_str(), nullptr)) {
200 if (GetLastError() != ERROR_ALREADY_EXISTS) {
201 FML_DLOG(ERROR) << "Could not create directory. "
202 << GetLastErrorMessage();
203 return {};
204 }
205 }
206 }
207
208 const DWORD creation_disposition = OPEN_EXISTING;
209
210 const DWORD flags = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS;
211
212 auto handle =
213 CreateFile(file_name.c_str(), // lpFileName
214 GetDesiredAccessFlags(permission), // dwDesiredAccess
215 GetShareFlags(permission), // dwShareMode
216 nullptr, // lpSecurityAttributes //
217 creation_disposition, // dwCreationDisposition //
218 flags, // dwFlagsAndAttributes //
219 nullptr // hTemplateFile //
220 );
221
222 if (handle == INVALID_HANDLE_VALUE) {
223 FML_DLOG(ERROR) << "Could not open file. " << GetLastErrorMessage();
224 return {};
225 }
226
227 return fml::UniqueFD{handle};
228 }
229
Duplicate(fml::UniqueFD::element_type descriptor)230 fml::UniqueFD Duplicate(fml::UniqueFD::element_type descriptor) {
231 if (descriptor == INVALID_HANDLE_VALUE) {
232 return fml::UniqueFD{};
233 }
234
235 HANDLE duplicated = INVALID_HANDLE_VALUE;
236
237 if (!::DuplicateHandle(
238 GetCurrentProcess(), // source process
239 descriptor, // source handle
240 GetCurrentProcess(), // target process
241 &duplicated, // target handle
242 0, // desired access (ignored because DUPLICATE_SAME_ACCESS)
243 FALSE, // inheritable
244 DUPLICATE_SAME_ACCESS) // options
245 ) {
246 return fml::UniqueFD{};
247 }
248
249 return fml::UniqueFD{duplicated};
250 }
251
IsDirectory(const fml::UniqueFD & directory)252 bool IsDirectory(const fml::UniqueFD& directory) {
253 BY_HANDLE_FILE_INFORMATION info;
254 if (!::GetFileInformationByHandle(directory.get(), &info)) {
255 FML_DLOG(ERROR) << "Could not get file information. "
256 << GetLastErrorMessage();
257 return false;
258 }
259 return info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
260 }
261
IsFile(const std::string & path)262 bool IsFile(const std::string& path) {
263 struct stat buf;
264 if (stat(path.c_str(), &buf) != 0)
265 return false;
266 return S_ISREG(buf.st_mode);
267 }
268
UnlinkDirectory(const char * path)269 bool UnlinkDirectory(const char* path) {
270 if (!::RemoveDirectory(ConvertToWString(path).c_str())) {
271 FML_DLOG(ERROR) << "Could not remove directory: '" << path << "'. "
272 << GetLastErrorMessage();
273 return false;
274 }
275 return true;
276 }
277
UnlinkDirectory(const fml::UniqueFD & base_directory,const char * path)278 bool UnlinkDirectory(const fml::UniqueFD& base_directory, const char* path) {
279 if (!::RemoveDirectory(
280 StringToWideString(GetAbsolutePath(base_directory, path)).c_str())) {
281 FML_DLOG(ERROR) << "Could not remove directory: '" << path << "'. "
282 << GetLastErrorMessage();
283 return false;
284 }
285 return true;
286 }
287
UnlinkFile(const char * path)288 bool UnlinkFile(const char* path) {
289 if (!::DeleteFile(ConvertToWString(path).c_str())) {
290 FML_DLOG(ERROR) << "Could not remove file: '" << path << "'. "
291 << GetLastErrorMessage();
292 return false;
293 }
294 return true;
295 }
296
UnlinkFile(const fml::UniqueFD & base_directory,const char * path)297 bool UnlinkFile(const fml::UniqueFD& base_directory, const char* path) {
298 if (!::DeleteFile(
299 StringToWideString(GetAbsolutePath(base_directory, path)).c_str())) {
300 FML_DLOG(ERROR) << "Could not remove file: '" << path << "'. "
301 << GetLastErrorMessage();
302 return false;
303 }
304 return true;
305 }
306
TruncateFile(const fml::UniqueFD & file,size_t size)307 bool TruncateFile(const fml::UniqueFD& file, size_t size) {
308 LARGE_INTEGER large_size;
309 large_size.QuadPart = size;
310 large_size.LowPart = SetFilePointer(file.get(), large_size.LowPart,
311 &large_size.HighPart, FILE_BEGIN);
312 if (large_size.LowPart == INVALID_SET_FILE_POINTER &&
313 GetLastError() != NO_ERROR) {
314 FML_DLOG(ERROR) << "Could not update file size. " << GetLastErrorMessage();
315 return false;
316 }
317
318 if (!::SetEndOfFile(file.get())) {
319 FML_DLOG(ERROR) << "Could not commit file size update. "
320 << GetLastErrorMessage();
321 return false;
322 }
323 return true;
324 }
325
FileExists(const fml::UniqueFD & base_directory,const char * path)326 bool FileExists(const fml::UniqueFD& base_directory, const char* path) {
327 return IsFile(GetAbsolutePath(base_directory, path).c_str());
328 }
329
WriteAtomically(const fml::UniqueFD & base_directory,const char * file_name,const Mapping & mapping)330 bool WriteAtomically(const fml::UniqueFD& base_directory,
331 const char* file_name,
332 const Mapping& mapping) {
333 if (file_name == nullptr) {
334 return false;
335 }
336
337 auto file_path = GetAbsolutePath(base_directory, file_name);
338 std::stringstream stream;
339 stream << file_path << ".temp";
340 auto temp_file_path = stream.str();
341
342 auto temp_file =
343 OpenFile(temp_file_path.c_str(), true, FilePermission::kReadWrite);
344
345 if (!temp_file.is_valid()) {
346 FML_DLOG(ERROR) << "Could not create temporary file.";
347 return false;
348 }
349
350 if (!TruncateFile(temp_file, mapping.GetSize())) {
351 FML_DLOG(ERROR) << "Could not truncate the file to the correct size. "
352 << GetLastErrorMessage();
353 return false;
354 }
355
356 {
357 FileMapping temp_file_mapping(temp_file, {FileMapping::Protection::kRead,
358 FileMapping::Protection::kWrite});
359 if (temp_file_mapping.GetSize() != mapping.GetSize()) {
360 FML_DLOG(ERROR) << "Temporary file mapping size was incorrect. Is "
361 << temp_file_mapping.GetSize() << ". Should be "
362 << mapping.GetSize() << ".";
363 return false;
364 }
365
366 if (temp_file_mapping.GetMutableMapping() == nullptr) {
367 FML_DLOG(ERROR) << "Temporary file does not have a mutable mapping.";
368 return false;
369 }
370
371 ::memcpy(temp_file_mapping.GetMutableMapping(), mapping.GetMapping(),
372 mapping.GetSize());
373
374 if (!::FlushViewOfFile(temp_file_mapping.GetMutableMapping(),
375 mapping.GetSize())) {
376 FML_DLOG(ERROR) << "Could not flush file view. " << GetLastErrorMessage();
377 return false;
378 }
379
380 if (!::FlushFileBuffers(temp_file.get())) {
381 FML_DLOG(ERROR) << "Could not flush file buffers. "
382 << GetLastErrorMessage();
383 return false;
384 }
385
386 // File mapping is detroyed.
387 }
388
389 temp_file.reset();
390
391 if (!::MoveFile(StringToWideString(temp_file_path).c_str(),
392 StringToWideString(file_path).c_str())) {
393 FML_DLOG(ERROR)
394 << "Could not replace temp file at correct path. File path: "
395 << file_path << ". Temp file path: " << temp_file_path << " "
396 << GetLastErrorMessage();
397 return false;
398 }
399
400 return true;
401 }
402
403 } // namespace fml
404