Flutter Engine
The Flutter Engine
directory_win.cc
Go to the documentation of this file.
1// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2// for details. All rights reserved. Use of this source code is governed by a
3// BSD-style license that can be found in the LICENSE file.
4
5#include "platform/globals.h"
6#if defined(DART_HOST_OS_WINDOWS)
7
8#include "bin/directory.h"
9
10#include <errno.h> // NOLINT
11#include <sys/stat.h> // NOLINT
12
13#include "bin/crypto.h"
14#include "bin/dartutils.h"
15#include "bin/file.h"
16#include "bin/file_win.h"
17#include "bin/namespace.h"
18#include "bin/utils.h"
19#include "bin/utils_win.h"
20#include "platform/syslog.h"
21#include "platform/utils.h"
22
23#undef DeleteFile
24
25namespace dart {
26namespace bin {
27
28PathBuffer::PathBuffer() : length_(0) {
29 data_ = calloc(MAX_LONG_PATH + 1, sizeof(wchar_t)); // NOLINT
30}
31
32PathBuffer::~PathBuffer() {
33 free(data_);
34}
35
36char* PathBuffer::AsString() const {
38 return nullptr;
39}
40
41wchar_t* PathBuffer::AsStringW() const {
42 return reinterpret_cast<wchar_t*>(data_);
43}
44
45const char* PathBuffer::AsScopedString() const {
46 return StringUtilsWin::WideToUtf8(AsStringW());
47}
48
49bool PathBuffer::Add(const char* name) {
50 const auto wide_name = Utf8ToWideChar(name);
51 return AddW(wide_name.get());
52}
53
54bool PathBuffer::AddW(const wchar_t* name) {
55 wchar_t* data = AsStringW();
56 int written =
57 _snwprintf(data + length_, MAX_LONG_PATH - length_, L"%s", name);
58 data[MAX_LONG_PATH] = L'\0';
59 if ((written <= MAX_LONG_PATH - length_) && (written >= 0) &&
60 (static_cast<size_t>(written) == wcsnlen(name, MAX_LONG_PATH + 1))) {
61 length_ += written;
62 return true;
63 } else {
64 SetLastError(ERROR_BUFFER_OVERFLOW);
65 return false;
66 }
67}
68
69void PathBuffer::Reset(intptr_t new_length) {
70 length_ = new_length;
71 AsStringW()[length_] = L'\0';
72}
73
74// If link_name points to a link, IsBrokenLink will return true if link_name
75// points to an invalid target.
76static bool IsBrokenLink(const wchar_t* link_name) {
77 HANDLE handle = CreateFileW(
78 link_name, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
79 nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
80 if (handle == INVALID_HANDLE_VALUE) {
81 return true;
82 } else {
83 CloseHandle(handle);
84 return false;
85 }
86}
87
88// A linked list structure holding a link target's unique file system ID.
89// Used to detect loops in the file system when listing recursively.
90struct LinkList {
91 DWORD volume;
92 DWORD id_low;
93 DWORD id_high;
94 LinkList* next;
95};
96
97// Forward declarations.
98static bool DeleteRecursively(PathBuffer* path);
99
100static ListType HandleFindFile(DirectoryListing* listing,
101 DirectoryListingEntry* entry,
102 const WIN32_FIND_DATAW& find_file_data) {
103 if (!listing->path_buffer().AddW(find_file_data.cFileName)) {
104 return kListError;
105 }
106 DWORD attributes = find_file_data.dwFileAttributes;
107 if ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
108 if (!listing->follow_links()) {
109 return kListLink;
110 }
111 HANDLE handle = CreateFileW(
112 listing->path_buffer().AsStringW(), 0,
113 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
114 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
115 if (handle == INVALID_HANDLE_VALUE) {
116 // Report as (broken) link.
117 return kListLink;
118 }
119 if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
120 // Check the seen link targets to see if we are in a file system loop.
121 LinkList current_link;
122 BY_HANDLE_FILE_INFORMATION info;
123 // Get info
124 if (!GetFileInformationByHandle(handle, &info)) {
126 CloseHandle(handle);
128 return kListError;
129 }
130 CloseHandle(handle);
131 current_link.volume = info.dwVolumeSerialNumber;
132 current_link.id_low = info.nFileIndexLow;
133 current_link.id_high = info.nFileIndexHigh;
134 current_link.next = entry->link();
135 LinkList* previous = entry->link();
136 while (previous != nullptr) {
137 if ((previous->volume == current_link.volume) &&
138 (previous->id_low == current_link.id_low) &&
139 (previous->id_high == current_link.id_high)) {
140 // Report the looping link as a link, rather than following it.
141 return kListLink;
142 }
143 previous = previous->next;
144 }
145 // Recurse into the directory, adding current link to the seen links list.
146 if ((wcscmp(find_file_data.cFileName, L".") == 0) ||
147 (wcscmp(find_file_data.cFileName, L"..") == 0)) {
148 return entry->Next(listing);
149 }
150 entry->set_link(new LinkList(current_link));
151 return kListDirectory;
152 }
153 }
154 if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
155 if ((wcscmp(find_file_data.cFileName, L".") == 0) ||
156 (wcscmp(find_file_data.cFileName, L"..") == 0)) {
157 return entry->Next(listing);
158 }
159 return kListDirectory;
160 } else {
161 return kListFile;
162 }
163}
164
165ListType DirectoryListingEntry::Next(DirectoryListing* listing) {
166 if (done_) {
167 return kListDone;
168 }
169
170 WIN32_FIND_DATAW find_file_data;
171
172 if (lister_ == 0) {
173 const wchar_t* tail = parent_ == nullptr ? L"*" : L"\\*";
174 if (!listing->path_buffer().AddW(tail)) {
175 done_ = true;
176 return kListError;
177 }
178
179 path_length_ = listing->path_buffer().length() - 1;
180
181 HANDLE find_handle =
182 FindFirstFileW(listing->path_buffer().AsStringW(), &find_file_data);
183
184 if (find_handle == INVALID_HANDLE_VALUE) {
185 done_ = true;
186 return kListError;
187 }
188
189 lister_ = reinterpret_cast<intptr_t>(find_handle);
190
191 listing->path_buffer().Reset(path_length_);
192
193 return HandleFindFile(listing, this, find_file_data);
194 }
195
196 // Reset.
197 listing->path_buffer().Reset(path_length_);
198 ResetLink();
199
200 if (FindNextFileW(reinterpret_cast<HANDLE>(lister_), &find_file_data) != 0) {
201 return HandleFindFile(listing, this, find_file_data);
202 }
203
204 done_ = true;
205
206 if (GetLastError() != ERROR_NO_MORE_FILES) {
207 return kListError;
208 }
209
210 return kListDone;
211}
212
213DirectoryListingEntry::~DirectoryListingEntry() {
214 ResetLink();
215 if (lister_ != 0) {
216 FindClose(reinterpret_cast<HANDLE>(lister_));
217 }
218}
219
220void DirectoryListingEntry::ResetLink() {
221 if ((link_ != nullptr) &&
222 ((parent_ == nullptr) || (parent_->link_ != link_))) {
223 delete link_;
224 link_ = nullptr;
225 }
226 if (parent_ != nullptr) {
227 link_ = parent_->link_;
228 }
229}
230
231namespace {
232class RecursiveDeleter {
233 public:
234 RecursiveDeleter() : path_() {}
235
236 // Delete the given directory recursively. Expects an absolute long prefixed
237 // path - which allows deletion to proceed without checking if path needs to
238 // be prefixed while recursing.
239 bool DeleteRecursively(const std::unique_ptr<wchar_t[]>& path) {
240 ASSERT(wcsncmp(path.get(), L"\\\\?\\", 4) == 0);
241 path_.Reset(0);
242 if (path == nullptr || !path_.AddW(path.get()) || path_.length() == 0) {
243 return false;
244 }
245
246 if (path_.AsStringW()[path_.length() - 1] == '\\') {
247 // Strip trailing slash otherwise FindFirstFileW will fail.
248 path_.Reset(path_.length() - 1);
249 }
250
251 return DeleteDirectory();
252 }
253
254 private:
255 const wchar_t* path() const { return path_.AsStringW(); }
256
257 bool DeleteDirectory() {
258 DWORD attributes = GetFileAttributesW(path());
259 if (attributes == INVALID_FILE_ATTRIBUTES) {
260 return false;
261 }
262
263 // If the directory is a junction, it's pointing to some other place in the
264 // filesystem that we do not want to recurse into.
265 if ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
266 // Just delete the junction itself.
267 return RemoveDirectoryW(path()) != 0;
268 }
269
270 // If it's a file, remove it directly.
271 if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
272 return DeleteFile();
273 }
274
275 if (!path_.AddW(L"\\*")) {
276 return false;
277 }
278
279 WIN32_FIND_DATAW find_file_data;
280 HANDLE find_handle = FindFirstFileW(path(), &find_file_data);
281
282 if (find_handle == INVALID_HANDLE_VALUE) {
283 return false;
284 }
285
286 // Adjust the path by removing the '*' used for the search.
287 const int path_length = path_.length() - 1;
288 path_.Reset(path_length);
289
290 do {
291 if (!DeleteEntry(&find_file_data)) {
292 break;
293 }
294 path_.Reset(path_length); // DeleteEntry adds to the path.
295 } while (FindNextFileW(find_handle, &find_file_data) != 0);
296
297 DWORD last_error = GetLastError();
298 // Always close handle.
299 FindClose(find_handle);
300 if (last_error != ERROR_NO_MORE_FILES) {
301 // Unexpected error, set and return.
302 SetLastError(last_error);
303 return false;
304 }
305 // All content deleted successfully, try to delete directory.
306 // Drop the "\" from the end of the path.
307 path_.Reset(path_length - 1);
308 return RemoveDirectoryW(path()) != 0;
309 }
310
311 bool DeleteEntry(LPWIN32_FIND_DATAW find_file_data) {
312 wchar_t* entry_name = find_file_data->cFileName;
313 if ((wcscmp(entry_name, L".") == 0) || (wcscmp(entry_name, L"..") == 0)) {
314 return true;
315 }
316
317 if (!path_.AddW(entry_name)) {
318 return false;
319 }
320
321 DWORD attributes = find_file_data->dwFileAttributes;
322 if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
323 return DeleteDirectory();
324 } else {
325 return DeleteFile();
326 }
327 }
328
329 bool DeleteFile() {
330 if (DeleteFileW(path()) != 0) {
331 return true;
332 }
333
334 // If we failed because the file is read-only, make it writeable and try
335 // again. This mirrors Linux/Mac where a directory containing read-only
336 // files can still be recursively deleted.
338 DWORD attributes = GetFileAttributesW(path());
339 if (attributes == INVALID_FILE_ATTRIBUTES) {
340 return false;
341 }
342
343 if ((attributes & FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY) {
344 attributes &= ~FILE_ATTRIBUTE_READONLY;
345
346 if (SetFileAttributesW(path(), attributes) == 0) {
347 return false;
348 }
349
350 return DeleteFileW(path()) != 0;
351 }
352 }
353
354 return false;
355 }
356
357 PathBuffer path_;
358};
359} // namespace
360
361Directory::ExistsResult Directory::Exists(const wchar_t* dir_name) {
362 DWORD attributes = GetFileAttributesW(dir_name);
363 if (attributes == INVALID_FILE_ATTRIBUTES) {
364 DWORD last_error = GetLastError();
365 if ((last_error == ERROR_FILE_NOT_FOUND) ||
366 (last_error == ERROR_PATH_NOT_FOUND)) {
367 return Directory::DOES_NOT_EXIST;
368 } else {
369 // We might not be able to get the file attributes for other
370 // reasons such as lack of permissions. In that case we do
371 // not know if the directory exists.
372 return Directory::UNKNOWN;
373 }
374 }
375 bool exists = (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
376 exists = exists && !IsBrokenLink(dir_name);
377 return exists ? Directory::EXISTS : Directory::DOES_NOT_EXIST;
378}
379
380Directory::ExistsResult Directory::Exists(Namespace* namespc,
381 const char* dir_name) {
382 const auto path = ToWinAPIDirectoryPath(dir_name);
383 return Exists(path.get());
384}
385
386char* Directory::CurrentNoScope() {
387 int length = GetCurrentDirectoryW(0, nullptr);
388 if (length == 0) {
389 return nullptr;
390 }
391 wchar_t* current = new wchar_t[length + 1];
392 GetCurrentDirectoryW(length + 1, current);
393 int utf8_len = WideCharToMultiByte(CP_UTF8, 0, current, -1, nullptr, 0,
394 nullptr, nullptr);
395 char* result = reinterpret_cast<char*>(malloc(utf8_len));
396 WideCharToMultiByte(CP_UTF8, 0, current, -1, result, utf8_len, nullptr,
397 nullptr);
398 delete[] current;
399 return result;
400}
401
402bool Directory::Create(Namespace* namespc, const char* dir_name) {
403 const auto path = ToWinAPIDirectoryPath(dir_name);
404 int create_status = CreateDirectoryW(path.get(), nullptr);
405 // If the directory already existed, treat it as a success.
406 if ((create_status == 0) && (GetLastError() == ERROR_ALREADY_EXISTS) &&
407 (Exists(path.get()) == EXISTS)) {
408 return true;
409 }
410 return (create_status != 0);
411}
412
413const char* Directory::SystemTemp(Namespace* namespc) {
414 PathBuffer path;
415 // Remove \ at end.
416 path.Reset(GetTempPathW(MAX_LONG_PATH, path.AsStringW()) - 1);
417 return path.AsScopedString();
418}
419
420// Creates a new temporary directory with a UUID as suffix.
421static const char* CreateTempFromUUID(const char* prefix) {
422 PathBuffer path;
423 const auto system_prefix = Utf8ToWideChar(prefix);
424 if (!path.AddW(system_prefix.get())) {
425 return nullptr;
426 }
427
428 // Length of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx is 36.
429 if (path.length() > MAX_LONG_PATH - 36) {
430 return nullptr;
431 }
432
433 UUID uuid;
434 RPC_STATUS status = UuidCreateSequential(&uuid);
435 if ((status != RPC_S_OK) && (status != RPC_S_UUID_LOCAL_ONLY)) {
436 return nullptr;
437 }
438 wchar_t* uuid_string;
439 status = UuidToStringW(&uuid, &uuid_string);
440 if (status != RPC_S_OK) {
441 return nullptr;
442 }
443
444 // RPC_WSTR is an unsigned short*, so we cast to wchar_t*.
445 if (!path.AddW(uuid_string)) {
446 return nullptr;
447 }
448 RpcStringFreeW(&uuid_string);
449 if (!CreateDirectoryW(path.AsStringW(), nullptr)) {
450 return nullptr;
451 }
452 return path.AsScopedString();
453}
454
455// Creates a new, unused directory, adding characters to the end of prefix, and
456// returns the directory's name.
457//
458// Creates this directory, with a default security descriptor inherited from its
459// parent directory. The return value is Dart_ScopeAllocated.
460//
461// First, attempts appending a suffix created from a random uint32_t. If that
462// name is already taken, falls back on using a UUID for the suffix.
463//
464// Note: More attempts at finding an available short suffix would more reliably
465// avoid a uuid suffix. We choose one attempt here because it is simpler, and
466// to have a small bound on the number of calls to CreateDirectoryW().
467const char* Directory::CreateTemp(Namespace* namespc, const char* prefix) {
468 PathBuffer path;
469 const auto system_prefix = Utf8ToWideChar(prefix);
470 if (!path.AddW(system_prefix.get())) {
471 return nullptr;
472 }
473
474 // Adding 8 hex digits.
475 if (path.length() > MAX_LONG_PATH - 8) {
476 // No fallback, there won't be enough room for the UUID, either.
477 return nullptr;
478 }
479
480 // First try a short suffix using the rng, then if that fails fall back on
481 // a uuid.
482 uint32_t suffix_bytes = 0;
483 const int kSuffixSize = sizeof(suffix_bytes);
484 if (!Crypto::GetRandomBytes(kSuffixSize,
485 reinterpret_cast<uint8_t*>(&suffix_bytes))) {
486 // Getting random bytes failed, maybe the UUID will work?
487 return CreateTempFromUUID(prefix);
488 }
489
490 // Two digits per byte plus null.
491 char suffix[kSuffixSize * 2 + 1];
492 Utils::SNPrint(suffix, sizeof(suffix), "%x", suffix_bytes);
493 if (!path.Add(suffix)) {
494 // Adding to the path failed, maybe because of low-memory. Don't fall back.
495 return nullptr;
496 }
497
498 if (!CreateDirectoryW(path.AsStringW(), nullptr)) {
499 // Creation failed, possibly because an entry with the name already exists.
500 // Fall back to using the UUID suffix.
501 return CreateTempFromUUID(prefix);
502 }
503 return path.AsScopedString();
504}
505
506bool Directory::Delete(Namespace* namespc,
507 const char* dir_name,
508 bool recursive) {
509 const auto path =
510 ToWinAPIDirectoryPath(dir_name, /*force_long_prefix=*/recursive);
511 bool result = false;
512 if (!recursive) {
513 if (File::GetType(path.get(), /*follow_links=*/true) ==
514 File::kIsDirectory) {
515 result = (RemoveDirectoryW(path.get()) != 0);
516 } else {
518 }
519 } else {
520 RecursiveDeleter deleter;
521 result = deleter.DeleteRecursively(path);
522 }
523 return result;
524}
525
526bool Directory::Rename(Namespace* namespc,
527 const char* old_name,
528 const char* new_name) {
529 const auto old_path = ToWinAPIDirectoryPath(old_name);
530 ExistsResult exists = Exists(old_path.get());
531 if (exists != EXISTS) {
533 return false;
534 }
535 const auto new_path = ToWinAPIDirectoryPath(new_name);
536 DWORD flags = MOVEFILE_WRITE_THROUGH;
537 int move_status = MoveFileExW(old_path.get(), new_path.get(), flags);
538 return (move_status != 0);
539}
540
541} // namespace bin
542} // namespace dart
543
544#endif // defined(DART_HOST_OS_WINDOWS)
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
static float next(float f)
static sk_sp< Effect > Create()
Definition: RefCntTest.cpp:117
#define UNREACHABLE()
Definition: assert.h:248
#define MAX_LONG_PATH
Definition: utils_win.h:15
#define ASSERT(E)
FlutterSemanticsFlag flags
const uint8_t uint32_t uint32_t GError ** error
GAsyncResult * result
size_t length
std::unique_ptr< wchar_t[]> Utf8ToWideChar(const char *path)
std::unique_ptr< wchar_t[]> ToWinAPIDirectoryPath(const char *path, bool force_long_prefix=false)
@ kListDone
Definition: directory.h:23
@ kListFile
Definition: directory.h:19
@ kListDirectory
Definition: directory.h:20
@ kListError
Definition: directory.h:22
@ kListLink
Definition: directory.h:21
Definition: dart_vm.cc:33
void * malloc(size_t size)
Definition: allocation.cc:19
void * calloc(size_t n, size_t size)
Definition: allocation.cc:11
static Dart_TypedData_Type GetType(intptr_t class_id)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition: switches.h:57
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
void Reset(SkPath *path)
Definition: path_ops.cc:40
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63
#define INVALID_HANDLE_VALUE
WINBASEAPI VOID WINAPI SetLastError(_In_ DWORD dwErrCode)
#define DeleteFile
WINBASEAPI _Check_return_ _Post_equals_last_error_ DWORD WINAPI GetLastError(VOID)
void * HANDLE
Definition: windows_types.h:36
unsigned long DWORD
Definition: windows_types.h:22
#define ERROR_FILE_NOT_FOUND
#define ERROR_ACCESS_DENIED