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
29namespace dart {
30namespace bin {
31
32class 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
45File::~File() {
46 if (!IsClosed() && handle_->fd() != _fileno(stdout) &&
47 handle_->fd() != _fileno(stderr)) {
48 Close();
49 }
50 delete handle_;
51}
52
53void 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
70intptr_t File::GetFD() {
71 return handle_->fd();
72}
73
74bool File::IsClosed() {
75 return handle_->fd() == kClosedFd;
76}
77
78MappedMemory* 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
137void MappedMemory::Unmap() {
138 BOOL result = VirtualFree(address_, 0, MEM_RELEASE);
139 ASSERT(result);
140 address_ = 0;
141 size_ = 0;
142}
143
144int64_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
149int64_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
182bool 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
202int64_t File::Position() {
203 ASSERT(handle_->fd() >= 0);
204 return _lseeki64(handle_->fd(), 0, SEEK_CUR);
205}
206
207bool File::SetPosition(int64_t position) {
208 ASSERT(handle_->fd() >= 0);
209 return _lseeki64(handle_->fd(), position, SEEK_SET) >= 0;
210}
211
212bool File::Truncate(int64_t length) {
213 ASSERT(handle_->fd() >= 0);
214 return _chsize_s(handle_->fd(), length) == 0;
215}
216
217bool File::Flush() {
218 ASSERT(handle_->fd());
219 return _commit(handle_->fd()) != -1;
220}
221
222bool 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
265int64_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
274File* 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
301File* 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
307Utils::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
330File* 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
338File* 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
354static 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
366bool 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
372bool 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
381bool 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.
391typedef 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
420static const int kReparseDataHeaderSize = sizeof(ULONG) + 2 * sizeof(USHORT);
421static 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.
426bool 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
451bool 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
457bool 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
477bool 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
493bool 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
521static 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
528static 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
615bool 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
648int64_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
657const 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
728void 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
747time_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
756time_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
765bool 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(), &times) == 0;
780}
781
782bool 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(), &times) == 0;
797}
798
799// Keep this function synchronized with the behavior
800// of `FileSystemEntity.isAbsolute` in file_system_entity.dart.
801bool 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
813const 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
862const char* File::PathSeparator() {
863 // This is already UTF-8 encoded.
864 return "\\";
865}
866
867const char* File::StringEscapedPathSeparator() {
868 // This is already UTF-8 encoded.
869 return "\\\\";
870}
871
872File::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
878File::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
908File::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