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/file.h" |
9 | |
10 | #include <WinIoCtl.h> // NOLINT |
11 | #include <fcntl.h> // NOLINT |
12 | #include <io.h> // NOLINT |
13 | #include <Shlwapi.h> // NOLINT |
14 | #undef StrDup // defined in Shlwapi.h as StrDupW |
15 | #include <stdio.h> // NOLINT |
16 | #include <string.h> // NOLINT |
17 | #include <sys/stat.h> // NOLINT |
18 | #include <sys/utime.h> // NOLINT |
19 | |
20 | #include "bin/builtin.h" |
21 | #include "bin/crypto.h" |
22 | #include "bin/directory.h" |
23 | #include "bin/namespace.h" |
24 | #include "bin/utils.h" |
25 | #include "bin/utils_win.h" |
26 | #include "platform/syslog.h" |
27 | #include "platform/utils.h" |
28 | |
29 | namespace dart { |
30 | namespace bin { |
31 | |
32 | class FileHandle { |
33 | public: |
34 | explicit FileHandle(int fd) : fd_(fd) {} |
35 | ~FileHandle() {} |
36 | int fd() const { return fd_; } |
37 | void set_fd(int fd) { fd_ = fd; } |
38 | |
39 | private: |
40 | int fd_; |
41 | |
42 | DISALLOW_COPY_AND_ASSIGN(FileHandle); |
43 | }; |
44 | |
45 | File::~File() { |
46 | if (!IsClosed() && handle_->fd() != _fileno(stdout) && |
47 | handle_->fd() != _fileno(stderr)) { |
48 | Close(); |
49 | } |
50 | delete handle_; |
51 | } |
52 | |
53 | void File::Close() { |
54 | ASSERT(handle_->fd() >= 0); |
55 | int closing_fd = handle_->fd(); |
56 | if ((closing_fd == _fileno(stdout)) || (closing_fd == _fileno(stderr))) { |
57 | int fd = _open("NUL" , _O_WRONLY); |
58 | ASSERT(fd >= 0); |
59 | _dup2(fd, closing_fd); |
60 | Utils::Close(fd); |
61 | } else { |
62 | int err = Utils::Close(closing_fd); |
63 | if (err != 0) { |
64 | Syslog::PrintErr("%s\n" , strerror(errno)); |
65 | } |
66 | } |
67 | handle_->set_fd(kClosedFd); |
68 | } |
69 | |
70 | intptr_t File::GetFD() { |
71 | return handle_->fd(); |
72 | } |
73 | |
74 | bool File::IsClosed() { |
75 | return handle_->fd() == kClosedFd; |
76 | } |
77 | |
78 | MappedMemory* File::Map(File::MapType type, |
79 | int64_t position, |
80 | int64_t length, |
81 | void* start) { |
82 | DWORD prot_alloc; |
83 | DWORD prot_final; |
84 | switch (type) { |
85 | case File::kReadOnly: |
86 | prot_alloc = PAGE_READWRITE; |
87 | prot_final = PAGE_READONLY; |
88 | break; |
89 | case File::kReadExecute: |
90 | prot_alloc = PAGE_EXECUTE_READWRITE; |
91 | prot_final = PAGE_EXECUTE_READ; |
92 | break; |
93 | case File::kReadWrite: |
94 | prot_alloc = PAGE_READWRITE; |
95 | prot_final = PAGE_READWRITE; |
96 | break; |
97 | } |
98 | |
99 | void* addr = start; |
100 | if (addr == nullptr) { |
101 | addr = VirtualAlloc(nullptr, length, MEM_COMMIT | MEM_RESERVE, prot_alloc); |
102 | if (addr == nullptr) { |
103 | Syslog::PrintErr("VirtualAlloc failed %d\n" , GetLastError()); |
104 | return nullptr; |
105 | } |
106 | } |
107 | |
108 | const int64_t remaining_length = Length() - position; |
109 | SetPosition(position); |
110 | if (!ReadFully(addr, Utils::Minimum(length, remaining_length))) { |
111 | Syslog::PrintErr("ReadFully failed %d\n" , GetLastError()); |
112 | if (start == nullptr) { |
113 | VirtualFree(addr, 0, MEM_RELEASE); |
114 | } |
115 | return nullptr; |
116 | } |
117 | |
118 | // If the requested mapping is larger than the file size, we should fill the |
119 | // extra memory with zeros. |
120 | if (length > remaining_length) { |
121 | memset(reinterpret_cast<uint8_t*>(addr) + remaining_length, 0, |
122 | length - remaining_length); |
123 | } |
124 | |
125 | DWORD old_prot; |
126 | bool result = VirtualProtect(addr, length, prot_final, &old_prot); |
127 | if (!result) { |
128 | Syslog::PrintErr("VirtualProtect failed %d\n" , GetLastError()); |
129 | if (start == nullptr) { |
130 | VirtualFree(addr, 0, MEM_RELEASE); |
131 | } |
132 | return nullptr; |
133 | } |
134 | return new MappedMemory(addr, length, /*should_unmap=*/start == nullptr); |
135 | } |
136 | |
137 | void MappedMemory::Unmap() { |
138 | BOOL result = VirtualFree(address_, 0, MEM_RELEASE); |
139 | ASSERT(result); |
140 | address_ = 0; |
141 | size_ = 0; |
142 | } |
143 | |
144 | int64_t File::Read(void* buffer, int64_t num_bytes) { |
145 | ASSERT(handle_->fd() >= 0); |
146 | return Utils::Read(handle_->fd(), buffer, num_bytes); |
147 | } |
148 | |
149 | int64_t File::Write(const void* buffer, int64_t num_bytes) { |
150 | int fd = handle_->fd(); |
151 | // Avoid narrowing conversion |
152 | ASSERT(fd >= 0 && num_bytes <= MAXDWORD && num_bytes >= 0); |
153 | HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd)); |
154 | DWORD written = 0; |
155 | BOOL result = WriteFile(handle, buffer, num_bytes, &written, NULL); |
156 | if (!result) { |
157 | return -1; |
158 | } |
159 | DWORD mode; |
160 | int64_t bytes_written = written; |
161 | if (GetConsoleMode(handle, &mode)) { |
162 | // If `handle` is for a console, then `written` may refer to the number of |
163 | // characters printed to the screen rather than the number of bytes of the |
164 | // buffer that were actually consumed. To compute the number of bytes that |
165 | // were actually consumed, we convert the buffer to a wchar_t using the |
166 | // console's current code page, filling as many characters as were |
167 | // printed, and then convert that many characters back to the encoding for |
168 | // the code page, which gives the number of bytes of `buffer` used to |
169 | // generate the characters that were printed. |
170 | wchar_t* wide = new wchar_t[written]; |
171 | int cp = GetConsoleOutputCP(); |
172 | MultiByteToWideChar(cp, 0, reinterpret_cast<const char*>(buffer), -1, wide, |
173 | written); |
174 | int buffer_len = |
175 | WideCharToMultiByte(cp, 0, wide, written, NULL, 0, NULL, NULL); |
176 | delete[] wide; |
177 | bytes_written = buffer_len; |
178 | } |
179 | return bytes_written; |
180 | } |
181 | |
182 | bool File::VPrint(const char* format, va_list args) { |
183 | // Measure. |
184 | va_list measure_args; |
185 | va_copy(measure_args, args); |
186 | intptr_t len = _vscprintf(format, measure_args); |
187 | va_end(measure_args); |
188 | |
189 | char* buffer = reinterpret_cast<char*>(malloc(len + 1)); |
190 | |
191 | // Print. |
192 | va_list print_args; |
193 | va_copy(print_args, args); |
194 | _vsnprintf(buffer, len + 1, format, print_args); |
195 | va_end(print_args); |
196 | |
197 | bool result = WriteFully(buffer, len); |
198 | free(buffer); |
199 | return result; |
200 | } |
201 | |
202 | int64_t File::Position() { |
203 | ASSERT(handle_->fd() >= 0); |
204 | return _lseeki64(handle_->fd(), 0, SEEK_CUR); |
205 | } |
206 | |
207 | bool File::SetPosition(int64_t position) { |
208 | ASSERT(handle_->fd() >= 0); |
209 | return _lseeki64(handle_->fd(), position, SEEK_SET) >= 0; |
210 | } |
211 | |
212 | bool File::Truncate(int64_t length) { |
213 | ASSERT(handle_->fd() >= 0); |
214 | return _chsize_s(handle_->fd(), length) == 0; |
215 | } |
216 | |
217 | bool File::Flush() { |
218 | ASSERT(handle_->fd()); |
219 | return _commit(handle_->fd()) != -1; |
220 | } |
221 | |
222 | bool File::Lock(File::LockType lock, int64_t start, int64_t end) { |
223 | ASSERT(handle_->fd() >= 0); |
224 | ASSERT((end == -1) || (end > start)); |
225 | HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(handle_->fd())); |
226 | OVERLAPPED overlapped; |
227 | ZeroMemory(&overlapped, sizeof(OVERLAPPED)); |
228 | |
229 | overlapped.Offset = Utils::Low32Bits(start); |
230 | overlapped.OffsetHigh = Utils::High32Bits(start); |
231 | |
232 | int64_t length = end == -1 ? 0 : end - start; |
233 | if (length == 0) { |
234 | length = kMaxInt64; |
235 | } |
236 | int32_t length_low = Utils::Low32Bits(length); |
237 | int32_t length_high = Utils::High32Bits(length); |
238 | |
239 | BOOL rc; |
240 | switch (lock) { |
241 | case File::kLockUnlock: |
242 | rc = UnlockFileEx(handle, 0, length_low, length_high, &overlapped); |
243 | break; |
244 | case File::kLockShared: |
245 | case File::kLockExclusive: |
246 | case File::kLockBlockingShared: |
247 | case File::kLockBlockingExclusive: { |
248 | DWORD flags = 0; |
249 | if ((lock == File::kLockShared) || (lock == File::kLockExclusive)) { |
250 | flags |= LOCKFILE_FAIL_IMMEDIATELY; |
251 | } |
252 | if ((lock == File::kLockExclusive) || |
253 | (lock == File::kLockBlockingExclusive)) { |
254 | flags |= LOCKFILE_EXCLUSIVE_LOCK; |
255 | } |
256 | rc = LockFileEx(handle, flags, 0, length_low, length_high, &overlapped); |
257 | break; |
258 | } |
259 | default: |
260 | UNREACHABLE(); |
261 | } |
262 | return rc; |
263 | } |
264 | |
265 | int64_t File::Length() { |
266 | ASSERT(handle_->fd() >= 0); |
267 | struct __stat64 st; |
268 | if (_fstat64(handle_->fd(), &st) == 0) { |
269 | return st.st_size; |
270 | } |
271 | return -1; |
272 | } |
273 | |
274 | File* File::FileOpenW(const wchar_t* system_name, FileOpenMode mode) { |
275 | int flags = O_RDONLY | O_BINARY | O_NOINHERIT; |
276 | if ((mode & kWrite) != 0) { |
277 | ASSERT((mode & kWriteOnly) == 0); |
278 | flags = (O_RDWR | O_CREAT | O_BINARY | O_NOINHERIT); |
279 | } |
280 | if ((mode & kWriteOnly) != 0) { |
281 | ASSERT((mode & kWrite) == 0); |
282 | flags = (O_WRONLY | O_CREAT | O_BINARY | O_NOINHERIT); |
283 | } |
284 | if ((mode & kTruncate) != 0) { |
285 | flags = flags | O_TRUNC; |
286 | } |
287 | int fd = _wopen(system_name, flags, 0666); |
288 | if (fd < 0) { |
289 | return NULL; |
290 | } |
291 | if ((((mode & kWrite) != 0) && ((mode & kTruncate) == 0)) || |
292 | (((mode & kWriteOnly) != 0) && ((mode & kTruncate) == 0))) { |
293 | int64_t position = _lseeki64(fd, 0, SEEK_END); |
294 | if (position < 0) { |
295 | return NULL; |
296 | } |
297 | } |
298 | return new File(new FileHandle(fd)); |
299 | } |
300 | |
301 | File* File::Open(Namespace* namespc, const char* path, FileOpenMode mode) { |
302 | Utf8ToWideScope system_name(path); |
303 | File* file = FileOpenW(system_name.wide(), mode); |
304 | return file; |
305 | } |
306 | |
307 | Utils::CStringUniquePtr File::UriToPath(const char* uri) { |
308 | UriDecoder uri_decoder(uri); |
309 | if (uri_decoder.decoded() == nullptr) { |
310 | SetLastError(ERROR_INVALID_NAME); |
311 | return Utils::CreateCStringUniquePtr(nullptr); |
312 | } |
313 | |
314 | Utf8ToWideScope uri_w(uri_decoder.decoded()); |
315 | if (!UrlIsFileUrlW(uri_w.wide())) { |
316 | return Utils::CreateCStringUniquePtr(Utils::StrDup(uri_decoder.decoded())); |
317 | } |
318 | wchar_t filename_w[MAX_PATH]; |
319 | DWORD filename_len = MAX_PATH; |
320 | HRESULT result = PathCreateFromUrlW(uri_w.wide(), filename_w, &filename_len, |
321 | /* dwFlags= */ 0); |
322 | if (result != S_OK) { |
323 | return Utils::CreateCStringUniquePtr(nullptr); |
324 | } |
325 | |
326 | WideToUtf8Scope utf8_path(filename_w); |
327 | return utf8_path.release(); |
328 | } |
329 | |
330 | File* File::OpenUri(Namespace* namespc, const char* uri, FileOpenMode mode) { |
331 | auto path = UriToPath(uri); |
332 | if (path == nullptr) { |
333 | return nullptr; |
334 | } |
335 | return Open(namespc, path.get(), mode); |
336 | } |
337 | |
338 | File* File::OpenStdio(int fd) { |
339 | int stdio_fd = -1; |
340 | switch (fd) { |
341 | case 1: |
342 | stdio_fd = _fileno(stdout); |
343 | break; |
344 | case 2: |
345 | stdio_fd = _fileno(stderr); |
346 | break; |
347 | default: |
348 | UNREACHABLE(); |
349 | } |
350 | _setmode(stdio_fd, _O_BINARY); |
351 | return new File(new FileHandle(stdio_fd)); |
352 | } |
353 | |
354 | static bool StatHelper(wchar_t* path, struct __stat64* st) { |
355 | int stat_status = _wstat64(path, st); |
356 | if (stat_status != 0) { |
357 | return false; |
358 | } |
359 | if ((st->st_mode & S_IFMT) != S_IFREG) { |
360 | SetLastError(ERROR_NOT_SUPPORTED); |
361 | return false; |
362 | } |
363 | return true; |
364 | } |
365 | |
366 | bool File::Exists(Namespace* namespc, const char* name) { |
367 | struct __stat64 st; |
368 | Utf8ToWideScope system_name(name); |
369 | return StatHelper(system_name.wide(), &st); |
370 | } |
371 | |
372 | bool File::ExistsUri(Namespace* namespc, const char* uri) { |
373 | UriDecoder uri_decoder(uri); |
374 | if (uri_decoder.decoded() == nullptr) { |
375 | SetLastError(ERROR_INVALID_NAME); |
376 | return false; |
377 | } |
378 | return File::Exists(namespc, uri_decoder.decoded()); |
379 | } |
380 | |
381 | bool File::Create(Namespace* namespc, const char* name) { |
382 | Utf8ToWideScope system_name(name); |
383 | int fd = _wopen(system_name.wide(), O_RDONLY | O_CREAT, 0666); |
384 | if (fd < 0) { |
385 | return false; |
386 | } |
387 | return (Utils::Close(fd) == 0); |
388 | } |
389 | |
390 | // This structure is needed for creating and reading Junctions. |
391 | typedef struct _REPARSE_DATA_BUFFER { |
392 | ULONG ReparseTag; |
393 | USHORT ReparseDataLength; |
394 | USHORT Reserved; |
395 | |
396 | union { |
397 | struct { |
398 | USHORT SubstituteNameOffset; |
399 | USHORT SubstituteNameLength; |
400 | USHORT PrintNameOffset; |
401 | USHORT PrintNameLength; |
402 | ULONG Flags; |
403 | WCHAR PathBuffer[1]; |
404 | } SymbolicLinkReparseBuffer; |
405 | |
406 | struct { |
407 | USHORT SubstituteNameOffset; |
408 | USHORT SubstituteNameLength; |
409 | USHORT PrintNameOffset; |
410 | USHORT PrintNameLength; |
411 | WCHAR PathBuffer[1]; |
412 | } MountPointReparseBuffer; |
413 | |
414 | struct { |
415 | UCHAR DataBuffer[1]; |
416 | } GenericReparseBuffer; |
417 | }; |
418 | } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; |
419 | |
420 | static const int kReparseDataHeaderSize = sizeof(ULONG) + 2 * sizeof(USHORT); |
421 | static const int kMountPointHeaderSize = 4 * sizeof(USHORT); |
422 | |
423 | // Note: CreateLink used to create junctions on Windows instead of true |
424 | // symbolic links. All File::*Link methods now support handling links created |
425 | // as junctions and symbolic links. |
426 | bool File::CreateLink(Namespace* namespc, |
427 | const char* utf8_name, |
428 | const char* utf8_target) { |
429 | Utf8ToWideScope name(utf8_name); |
430 | Utf8ToWideScope target(utf8_target); |
431 | DWORD flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; |
432 | |
433 | File::Type type = File::GetType(namespc, utf8_target, true); |
434 | if (type == kIsDirectory) { |
435 | flags |= SYMBOLIC_LINK_FLAG_DIRECTORY; |
436 | } |
437 | |
438 | int create_status = CreateSymbolicLinkW(name.wide(), target.wide(), flags); |
439 | |
440 | // If running on a Windows 10 build older than 14972, an invalid parameter |
441 | // error will be returned when trying to use the |
442 | // SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE flag. Retry without the flag. |
443 | if ((create_status == 0) && (GetLastError() == ERROR_INVALID_PARAMETER)) { |
444 | flags &= ~SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; |
445 | create_status = CreateSymbolicLinkW(name.wide(), target.wide(), flags); |
446 | } |
447 | |
448 | return (create_status != 0); |
449 | } |
450 | |
451 | bool File::Delete(Namespace* namespc, const char* name) { |
452 | Utf8ToWideScope system_name(name); |
453 | int status = _wremove(system_name.wide()); |
454 | return status != -1; |
455 | } |
456 | |
457 | bool File::DeleteLink(Namespace* namespc, const char* name) { |
458 | Utf8ToWideScope system_name(name); |
459 | bool result = false; |
460 | DWORD attributes = GetFileAttributesW(system_name.wide()); |
461 | if ((attributes == INVALID_FILE_ATTRIBUTES) || |
462 | ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0)) { |
463 | SetLastError(ERROR_NOT_A_REPARSE_POINT); |
464 | return false; |
465 | } |
466 | if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { |
467 | // It's a junction, which is a special type of directory, or a symbolic |
468 | // link to a directory. Remove the directory. |
469 | result = (RemoveDirectoryW(system_name.wide()) != 0); |
470 | } else { |
471 | // Symbolic link to a file. Remove the file. |
472 | result = (DeleteFileW(system_name.wide()) != 0); |
473 | } |
474 | return result; |
475 | } |
476 | |
477 | bool File::Rename(Namespace* namespc, |
478 | const char* old_path, |
479 | const char* new_path) { |
480 | File::Type type = GetType(namespc, old_path, false); |
481 | if (type != kIsFile) { |
482 | SetLastError(ERROR_FILE_NOT_FOUND); |
483 | return false; |
484 | } |
485 | Utf8ToWideScope system_old_path(old_path); |
486 | Utf8ToWideScope system_new_path(new_path); |
487 | DWORD flags = MOVEFILE_WRITE_THROUGH | MOVEFILE_REPLACE_EXISTING; |
488 | int move_status = |
489 | MoveFileExW(system_old_path.wide(), system_new_path.wide(), flags); |
490 | return (move_status != 0); |
491 | } |
492 | |
493 | bool File::RenameLink(Namespace* namespc, |
494 | const char* old_path, |
495 | const char* new_path) { |
496 | File::Type type = GetType(namespc, old_path, false); |
497 | if (type != kIsLink) { |
498 | SetLastError(ERROR_FILE_NOT_FOUND); |
499 | return false; |
500 | } |
501 | Utf8ToWideScope system_old_path(old_path); |
502 | Utf8ToWideScope system_new_path(new_path); |
503 | DWORD flags = MOVEFILE_WRITE_THROUGH | MOVEFILE_REPLACE_EXISTING; |
504 | |
505 | // Junction links on Windows appear as special directories. MoveFileExW's |
506 | // MOVEFILE_REPLACE_EXISTING does not allow for replacement of directories, |
507 | // so we need to remove it before renaming a link. This step is only |
508 | // necessary for junctions created by the old Link.create implementation. |
509 | if ((Directory::Exists(namespc, new_path) == Directory::EXISTS) && |
510 | (GetType(namespc, new_path, false) == kIsLink)) { |
511 | // Bail out if the DeleteLink call fails. |
512 | if (!DeleteLink(namespc, new_path)) { |
513 | return false; |
514 | } |
515 | } |
516 | int move_status = |
517 | MoveFileExW(system_old_path.wide(), system_new_path.wide(), flags); |
518 | return (move_status != 0); |
519 | } |
520 | |
521 | static wchar_t* CopyToDartScopeString(wchar_t* string) { |
522 | wchar_t* wide_path = reinterpret_cast<wchar_t*>( |
523 | Dart_ScopeAllocate(MAX_PATH * sizeof(wchar_t) + 1)); |
524 | wcscpy(wide_path, string); |
525 | return wide_path; |
526 | } |
527 | |
528 | static wchar_t* CopyIntoTempFile(const char* src, const char* dest) { |
529 | // This function will copy the file to a temp file in the destination |
530 | // directory and return the path of temp file. |
531 | // Creating temp file name has the same logic as Directory::CreateTemp(), |
532 | // which tries with the rng and falls back to a uuid if it failed. |
533 | const char* last_back_slash = strrchr(dest, '\\'); |
534 | // It is possible the path uses forwardslash as path separator. |
535 | const char* last_forward_slash = strrchr(dest, '/'); |
536 | const char* last_path_separator = NULL; |
537 | if (last_back_slash == NULL && last_forward_slash == NULL) { |
538 | return NULL; |
539 | } else if (last_forward_slash != NULL && last_forward_slash != NULL) { |
540 | // If both types occur in the path, use the one closer to the end. |
541 | if (last_back_slash - dest > last_forward_slash - dest) { |
542 | last_path_separator = last_back_slash; |
543 | } else { |
544 | last_path_separator = last_forward_slash; |
545 | } |
546 | } else { |
547 | last_path_separator = |
548 | (last_forward_slash == NULL) ? last_back_slash : last_forward_slash; |
549 | } |
550 | int length_of_parent_dir = last_path_separator - dest + 1; |
551 | if (length_of_parent_dir + 8 > MAX_PATH) { |
552 | return NULL; |
553 | } |
554 | uint32_t suffix_bytes = 0; |
555 | const int kSuffixSize = sizeof(suffix_bytes); |
556 | if (Crypto::GetRandomBytes(kSuffixSize, |
557 | reinterpret_cast<uint8_t*>(&suffix_bytes))) { |
558 | PathBuffer buffer; |
559 | char* dir = reinterpret_cast<char*>( |
560 | Dart_ScopeAllocate(1 + sizeof(char) * length_of_parent_dir)); |
561 | memmove(dir, dest, length_of_parent_dir); |
562 | dir[length_of_parent_dir] = '\0'; |
563 | if (!buffer.Add(dir)) { |
564 | return NULL; |
565 | } |
566 | |
567 | char suffix[8 + 1]; |
568 | Utils::SNPrint(suffix, sizeof(suffix), "%x" , suffix_bytes); |
569 | Utf8ToWideScope source_path(src); |
570 | if (!buffer.Add(suffix)) { |
571 | return NULL; |
572 | } |
573 | if (CopyFileExW(source_path.wide(), buffer.AsStringW(), NULL, NULL, NULL, |
574 | 0) != 0) { |
575 | return CopyToDartScopeString(buffer.AsStringW()); |
576 | } |
577 | // If CopyFileExW() fails to copy to a temp file with random hex, fall |
578 | // back to copy to a uuid temp file. |
579 | } |
580 | // UUID has a total of 36 characters in the form of |
581 | // xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx. |
582 | if (length_of_parent_dir + 36 > MAX_PATH) { |
583 | return NULL; |
584 | } |
585 | UUID uuid; |
586 | RPC_STATUS status = UuidCreateSequential(&uuid); |
587 | if ((status != RPC_S_OK) && (status != RPC_S_UUID_LOCAL_ONLY)) { |
588 | return NULL; |
589 | } |
590 | RPC_WSTR uuid_string; |
591 | status = UuidToStringW(&uuid, &uuid_string); |
592 | if (status != RPC_S_OK) { |
593 | return NULL; |
594 | } |
595 | PathBuffer buffer; |
596 | char* dir = reinterpret_cast<char*>( |
597 | Dart_ScopeAllocate(1 + sizeof(char) * length_of_parent_dir)); |
598 | memmove(dir, dest, length_of_parent_dir); |
599 | dir[length_of_parent_dir] = '\0'; |
600 | Utf8ToWideScope dest_path(dir); |
601 | if (!buffer.AddW(dest_path.wide()) || |
602 | !buffer.AddW(reinterpret_cast<wchar_t*>(uuid_string))) { |
603 | return NULL; |
604 | } |
605 | |
606 | RpcStringFreeW(&uuid_string); |
607 | Utf8ToWideScope source_path(src); |
608 | if (CopyFileExW(source_path.wide(), buffer.AsStringW(), NULL, NULL, NULL, |
609 | 0) != 0) { |
610 | return CopyToDartScopeString(buffer.AsStringW()); |
611 | } |
612 | return NULL; |
613 | } |
614 | |
615 | bool File::Copy(Namespace* namespc, |
616 | const char* old_path, |
617 | const char* new_path) { |
618 | File::Type type = GetType(namespc, old_path, false); |
619 | if (type != kIsFile) { |
620 | SetLastError(ERROR_FILE_NOT_FOUND); |
621 | return false; |
622 | } |
623 | |
624 | wchar_t* temp_file = CopyIntoTempFile(old_path, new_path); |
625 | if (temp_file == NULL) { |
626 | // If temp file creation fails, fall back on doing a direct copy. |
627 | Utf8ToWideScope system_old_path(old_path); |
628 | Utf8ToWideScope system_new_path(new_path); |
629 | return CopyFileExW(system_old_path.wide(), system_new_path.wide(), NULL, |
630 | NULL, NULL, 0) != 0; |
631 | } |
632 | Utf8ToWideScope system_new_dest(new_path); |
633 | |
634 | // Remove the existing file. Otherwise, renaming will fail. |
635 | if (Exists(namespc, new_path)) { |
636 | DeleteFileW(system_new_dest.wide()); |
637 | } |
638 | |
639 | if (!MoveFileW(temp_file, system_new_dest.wide())) { |
640 | DWORD error = GetLastError(); |
641 | DeleteFileW(temp_file); |
642 | SetLastError(error); |
643 | return false; |
644 | } |
645 | return true; |
646 | } |
647 | |
648 | int64_t File::LengthFromPath(Namespace* namespc, const char* name) { |
649 | struct __stat64 st; |
650 | Utf8ToWideScope system_name(name); |
651 | if (!StatHelper(system_name.wide(), &st)) { |
652 | return -1; |
653 | } |
654 | return st.st_size; |
655 | } |
656 | |
657 | const char* File::LinkTarget(Namespace* namespc, |
658 | const char* pathname, |
659 | char* dest, |
660 | int dest_size) { |
661 | const wchar_t* name = StringUtilsWin::Utf8ToWide(pathname); |
662 | HANDLE dir_handle = CreateFileW( |
663 | name, GENERIC_READ, |
664 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, |
665 | OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, |
666 | NULL); |
667 | if (dir_handle == INVALID_HANDLE_VALUE) { |
668 | return NULL; |
669 | } |
670 | |
671 | int buffer_size = |
672 | sizeof(REPARSE_DATA_BUFFER) + 2 * (MAX_PATH + 1) * sizeof(WCHAR); |
673 | REPARSE_DATA_BUFFER* buffer = |
674 | reinterpret_cast<REPARSE_DATA_BUFFER*>(Dart_ScopeAllocate(buffer_size)); |
675 | DWORD received_bytes; // Value is not used. |
676 | int result = DeviceIoControl(dir_handle, FSCTL_GET_REPARSE_POINT, NULL, 0, |
677 | buffer, buffer_size, &received_bytes, NULL); |
678 | if (result == 0) { |
679 | DWORD error = GetLastError(); |
680 | CloseHandle(dir_handle); |
681 | SetLastError(error); |
682 | return NULL; |
683 | } |
684 | if (CloseHandle(dir_handle) == 0) { |
685 | return NULL; |
686 | } |
687 | |
688 | wchar_t* target; |
689 | size_t target_offset; |
690 | size_t target_length; |
691 | if (buffer->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) { |
692 | target = buffer->MountPointReparseBuffer.PathBuffer; |
693 | target_offset = buffer->MountPointReparseBuffer.SubstituteNameOffset; |
694 | target_length = buffer->MountPointReparseBuffer.SubstituteNameLength; |
695 | } else if (buffer->ReparseTag == IO_REPARSE_TAG_SYMLINK) { |
696 | target = buffer->SymbolicLinkReparseBuffer.PathBuffer; |
697 | target_offset = buffer->SymbolicLinkReparseBuffer.SubstituteNameOffset; |
698 | target_length = buffer->SymbolicLinkReparseBuffer.SubstituteNameLength; |
699 | } else { // Not a junction or a symbolic link. |
700 | SetLastError(ERROR_NOT_A_REPARSE_POINT); |
701 | return NULL; |
702 | } |
703 | |
704 | target_offset /= sizeof(wchar_t); // Offset and length are in bytes. |
705 | target_length /= sizeof(wchar_t); |
706 | target += target_offset; |
707 | // Remove "\??\" from beginning of target. |
708 | if ((target_length > 4) && (wcsncmp(L"\\??\\" , target, 4) == 0)) { |
709 | target += 4; |
710 | target_length -= 4; |
711 | } |
712 | int utf8_length = WideCharToMultiByte(CP_UTF8, 0, target, target_length, NULL, |
713 | 0, NULL, NULL); |
714 | if (dest_size > 0 && dest_size <= utf8_length) { |
715 | return NULL; |
716 | } |
717 | if (dest == NULL) { |
718 | dest = DartUtils::ScopedCString(utf8_length + 1); |
719 | } |
720 | if (0 == WideCharToMultiByte(CP_UTF8, 0, target, target_length, dest, |
721 | utf8_length, NULL, NULL)) { |
722 | return NULL; |
723 | } |
724 | dest[utf8_length] = '\0'; |
725 | return dest; |
726 | } |
727 | |
728 | void File::Stat(Namespace* namespc, const char* name, int64_t* data) { |
729 | File::Type type = GetType(namespc, name, false); |
730 | data[kType] = type; |
731 | if (type != kDoesNotExist) { |
732 | struct _stat64 st; |
733 | Utf8ToWideScope system_name(name); |
734 | int stat_status = _wstat64(system_name.wide(), &st); |
735 | if (stat_status == 0) { |
736 | data[kCreatedTime] = st.st_ctime * 1000; |
737 | data[kModifiedTime] = st.st_mtime * 1000; |
738 | data[kAccessedTime] = st.st_atime * 1000; |
739 | data[kMode] = st.st_mode; |
740 | data[kSize] = st.st_size; |
741 | } else { |
742 | data[kType] = File::kDoesNotExist; |
743 | } |
744 | } |
745 | } |
746 | |
747 | time_t File::LastAccessed(Namespace* namespc, const char* name) { |
748 | struct __stat64 st; |
749 | Utf8ToWideScope system_name(name); |
750 | if (!StatHelper(system_name.wide(), &st)) { |
751 | return -1; |
752 | } |
753 | return st.st_atime; |
754 | } |
755 | |
756 | time_t File::LastModified(Namespace* namespc, const char* name) { |
757 | struct __stat64 st; |
758 | Utf8ToWideScope system_name(name); |
759 | if (!StatHelper(system_name.wide(), &st)) { |
760 | return -1; |
761 | } |
762 | return st.st_mtime; |
763 | } |
764 | |
765 | bool File::SetLastAccessed(Namespace* namespc, |
766 | const char* name, |
767 | int64_t millis) { |
768 | // First get the current times. |
769 | struct __stat64 st; |
770 | Utf8ToWideScope system_name(name); |
771 | if (!StatHelper(system_name.wide(), &st)) { |
772 | return false; |
773 | } |
774 | |
775 | // Set the new time: |
776 | struct __utimbuf64 times; |
777 | times.actime = millis / kMillisecondsPerSecond; |
778 | times.modtime = st.st_mtime; |
779 | return _wutime64(system_name.wide(), ×) == 0; |
780 | } |
781 | |
782 | bool File::SetLastModified(Namespace* namespc, |
783 | const char* name, |
784 | int64_t millis) { |
785 | // First get the current times. |
786 | struct __stat64 st; |
787 | Utf8ToWideScope system_name(name); |
788 | if (!StatHelper(system_name.wide(), &st)) { |
789 | return false; |
790 | } |
791 | |
792 | // Set the new time: |
793 | struct __utimbuf64 times; |
794 | times.actime = st.st_atime; |
795 | times.modtime = millis / kMillisecondsPerSecond; |
796 | return _wutime64(system_name.wide(), ×) == 0; |
797 | } |
798 | |
799 | // Keep this function synchronized with the behavior |
800 | // of `FileSystemEntity.isAbsolute` in file_system_entity.dart. |
801 | bool File::IsAbsolutePath(const char* pathname) { |
802 | if (pathname == NULL) return false; |
803 | char first = pathname[0]; |
804 | if (pathname == 0) return false; |
805 | char second = pathname[1]; |
806 | if (first == '\\' && second == '\\') return true; |
807 | if (second != ':') return false; |
808 | first |= 0x20; |
809 | char third = pathname[2]; |
810 | return (first >= 'a') && (first <= 'z') && (third == '\\' || third == '/'); |
811 | } |
812 | |
813 | const char* File::GetCanonicalPath(Namespace* namespc, |
814 | const char* pathname, |
815 | char* dest, |
816 | int dest_size) { |
817 | Utf8ToWideScope system_name(pathname); |
818 | HANDLE file_handle = |
819 | CreateFileW(system_name.wide(), 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, |
820 | FILE_FLAG_BACKUP_SEMANTICS, NULL); |
821 | if (file_handle == INVALID_HANDLE_VALUE) { |
822 | return NULL; |
823 | } |
824 | wchar_t dummy_buffer[1]; |
825 | int required_size = |
826 | GetFinalPathNameByHandle(file_handle, dummy_buffer, 0, VOLUME_NAME_DOS); |
827 | if (required_size == 0) { |
828 | DWORD error = GetLastError(); |
829 | CloseHandle(file_handle); |
830 | SetLastError(error); |
831 | return NULL; |
832 | } |
833 | auto path = std::unique_ptr<wchar_t[]>(new wchar_t[required_size]); |
834 | int result_size = GetFinalPathNameByHandle(file_handle, path.get(), |
835 | required_size, VOLUME_NAME_DOS); |
836 | ASSERT(result_size <= required_size - 1); |
837 | CloseHandle(file_handle); |
838 | |
839 | // Remove leading \\?\ if possible, unless input used it. |
840 | int offset = 0; |
841 | if ((result_size < MAX_PATH - 1 + 4) && (result_size > 4) && |
842 | (wcsncmp(path.get(), L"\\\\?\\" , 4) == 0) && |
843 | (wcsncmp(system_name.wide(), L"\\\\?\\" , 4) != 0)) { |
844 | offset = 4; |
845 | } |
846 | int utf8_size = WideCharToMultiByte(CP_UTF8, 0, path.get() + offset, -1, |
847 | nullptr, 0, nullptr, nullptr); |
848 | if (dest == NULL) { |
849 | dest = DartUtils::ScopedCString(utf8_size); |
850 | dest_size = utf8_size; |
851 | } |
852 | if (dest_size != 0) { |
853 | ASSERT(utf8_size <= dest_size); |
854 | } |
855 | if (0 == WideCharToMultiByte(CP_UTF8, 0, path.get() + offset, -1, dest, |
856 | dest_size, NULL, NULL)) { |
857 | return NULL; |
858 | } |
859 | return dest; |
860 | } |
861 | |
862 | const char* File::PathSeparator() { |
863 | // This is already UTF-8 encoded. |
864 | return "\\" ; |
865 | } |
866 | |
867 | const char* File::StringEscapedPathSeparator() { |
868 | // This is already UTF-8 encoded. |
869 | return "\\\\" ; |
870 | } |
871 | |
872 | File::StdioHandleType File::GetStdioHandleType(int fd) { |
873 | // Treat all stdio handles as pipes. The Windows event handler and |
874 | // socket code will handle the different handle types. |
875 | return kPipe; |
876 | } |
877 | |
878 | File::Type File::GetType(Namespace* namespc, |
879 | const char* pathname, |
880 | bool follow_links) { |
881 | // Convert to wchar_t string. |
882 | Utf8ToWideScope name(pathname); |
883 | DWORD attributes = GetFileAttributesW(name.wide()); |
884 | File::Type result = kIsFile; |
885 | if (attributes == INVALID_FILE_ATTRIBUTES) { |
886 | result = kDoesNotExist; |
887 | } else if ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { |
888 | if (follow_links) { |
889 | HANDLE dir_handle = |
890 | CreateFileW(name.wide(), 0, |
891 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
892 | NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); |
893 | if (dir_handle == INVALID_HANDLE_VALUE) { |
894 | result = File::kIsLink; |
895 | } else { |
896 | CloseHandle(dir_handle); |
897 | result = File::kIsDirectory; |
898 | } |
899 | } else { |
900 | result = kIsLink; |
901 | } |
902 | } else if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { |
903 | result = kIsDirectory; |
904 | } |
905 | return result; |
906 | } |
907 | |
908 | File::Identical File::AreIdentical(Namespace* namespc_1, |
909 | const char* file_1, |
910 | Namespace* namespc_2, |
911 | const char* file_2) { |
912 | USE(namespc_1); |
913 | USE(namespc_2); |
914 | BY_HANDLE_FILE_INFORMATION file_info[2]; |
915 | const char* file_names[2] = {file_1, file_2}; |
916 | for (int i = 0; i < 2; ++i) { |
917 | Utf8ToWideScope wide_name(file_names[i]); |
918 | HANDLE file_handle = CreateFileW( |
919 | wide_name.wide(), 0, |
920 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, |
921 | OPEN_EXISTING, |
922 | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL); |
923 | if (file_handle == INVALID_HANDLE_VALUE) { |
924 | return File::kError; |
925 | } |
926 | int result = GetFileInformationByHandle(file_handle, &file_info[i]); |
927 | if (result == 0) { |
928 | DWORD error = GetLastError(); |
929 | CloseHandle(file_handle); |
930 | SetLastError(error); |
931 | return File::kError; |
932 | } |
933 | if (CloseHandle(file_handle) == 0) { |
934 | return File::kError; |
935 | } |
936 | } |
937 | if ((file_info[0].dwVolumeSerialNumber == |
938 | file_info[1].dwVolumeSerialNumber) && |
939 | (file_info[0].nFileIndexHigh == file_info[1].nFileIndexHigh) && |
940 | (file_info[0].nFileIndexLow == file_info[1].nFileIndexLow)) { |
941 | return kIdentical; |
942 | } else { |
943 | return kDifferent; |
944 | } |
945 | } |
946 | |
947 | } // namespace bin |
948 | } // namespace dart |
949 | |
950 | #endif // defined(HOST_OS_WINDOWS) |
951 | |