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