diff --git a/pkg/test_runner/lib/src/options.dart b/pkg/test_runner/lib/src/options.dart index cc534cabd0f..5c991eab1a0 100644 --- a/pkg/test_runner/lib/src/options.dart +++ b/pkg/test_runner/lib/src/options.dart @@ -637,6 +637,11 @@ has been specified on the command line.''') var progress = Progress.find(data["progress"] as String); var nnbdMode = NnbdMode.find(data["nnbd"] as String); + // TODO: deflaking should report logs. + if (data['repeat'] == 5) { + progress = Progress.verbose; + } + void addConfiguration(Configuration innerConfiguration, [String? namedConfiguration]) { var configuration = TestConfiguration( diff --git a/runtime/bin/directory.h b/runtime/bin/directory.h index 3226e387ada..43904b753ba 100644 --- a/runtime/bin/directory.h +++ b/runtime/bin/directory.h @@ -242,6 +242,11 @@ class Directory { enum ExistsResult { UNKNOWN, EXISTS, DOES_NOT_EXIST }; static void List(DirectoryListing* listing); + +#if defined(DART_HOST_OS_WINDOWS) + static ExistsResult Exists(const wchar_t* path); +#endif + static ExistsResult Exists(Namespace* namespc, const char* path); // Returns the current working directory. The caller must call diff --git a/runtime/bin/directory_win.cc b/runtime/bin/directory_win.cc index db4ec43cff3..0fd6c77aefd 100644 --- a/runtime/bin/directory_win.cc +++ b/runtime/bin/directory_win.cc @@ -47,8 +47,8 @@ const char* PathBuffer::AsScopedString() const { } bool PathBuffer::Add(const char* name) { - Utf8ToWideScope wide_name(name); - return AddW(wide_name.wide()); + const auto wide_name = Utf8ToWideChar(name); + return AddW(wide_name.get()); } bool PathBuffer::AddW(const wchar_t* name) { @@ -228,114 +228,137 @@ void DirectoryListingEntry::ResetLink() { } } -static bool DeleteFile(const wchar_t* file_name, PathBuffer* path) { - if (!path->AddW(file_name)) { - return false; +namespace { +class RecursiveDeleter { + public: + RecursiveDeleter() : path_() {} + + // Delete the given directory recursively. Expects an absolute long prefixed + // path - which allows deletion to proceed without checking if path needs to + // be prefixed while recursing. + bool DeleteRecursively(const std::unique_ptr& path) { + ASSERT(wcsncmp(path.get(), L"\\\\?\\", 4) == 0); + path_.Reset(0); + if (path == nullptr || !path_.AddW(path.get()) || path_.length() == 0) { + return false; + } + + if (path_.AsStringW()[path_.length() - 1] == '\\') { + // Strip trailing slash otherwise FindFirstFileW will fail. + path_.Reset(path_.length() - 1); + } + + return DeleteDirectory(); } - if (DeleteFileW(path->AsStringW()) != 0) { - return true; - } + private: + const wchar_t* path() const { return path_.AsStringW(); } - // If we failed because the file is read-only, make it writeable and try - // again. This mirrors Linux/Mac where a directory containing read-only files - // can still be recursively deleted. - if (GetLastError() == ERROR_ACCESS_DENIED) { - DWORD attributes = GetFileAttributesW(path->AsStringW()); + bool DeleteDirectory() { + DWORD attributes = GetFileAttributesW(path()); if (attributes == INVALID_FILE_ATTRIBUTES) { return false; } - if ((attributes & FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY) { - attributes &= ~FILE_ATTRIBUTE_READONLY; + // If the directory is a junction, it's pointing to some other place in the + // filesystem that we do not want to recurse into. + if ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { + // Just delete the junction itself. + return RemoveDirectoryW(path()) != 0; + } - if (SetFileAttributesW(path->AsStringW(), attributes) == 0) { + // If it's a file, remove it directly. + if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { + return DeleteFile(); + } + + if (!path_.AddW(L"\\*")) { + return false; + } + + WIN32_FIND_DATAW find_file_data; + HANDLE find_handle = FindFirstFileW(path(), &find_file_data); + + if (find_handle == INVALID_HANDLE_VALUE) { + return false; + } + + // Adjust the path by removing the '*' used for the search. + const int path_length = path_.length() - 1; + path_.Reset(path_length); + + do { + if (!DeleteEntry(&find_file_data)) { + break; + } + path_.Reset(path_length); // DeleteEntry adds to the path. + } while (FindNextFileW(find_handle, &find_file_data) != 0); + + DWORD last_error = GetLastError(); + // Always close handle. + FindClose(find_handle); + if (last_error != ERROR_NO_MORE_FILES) { + // Unexpected error, set and return. + SetLastError(last_error); + return false; + } + // All content deleted successfully, try to delete directory. + // Drop the "\" from the end of the path. + path_.Reset(path_length - 1); + return RemoveDirectoryW(path()) != 0; + } + + bool DeleteEntry(LPWIN32_FIND_DATAW find_file_data) { + wchar_t* entry_name = find_file_data->cFileName; + if ((wcscmp(entry_name, L".") == 0) || (wcscmp(entry_name, L"..") == 0)) { + return true; + } + + if (!path_.AddW(entry_name)) { + return false; + } + + DWORD attributes = find_file_data->dwFileAttributes; + if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { + return DeleteDirectory(); + } else { + return DeleteFile(); + } + } + + bool DeleteFile() { + if (DeleteFileW(path()) != 0) { + return true; + } + + // If we failed because the file is read-only, make it writeable and try + // again. This mirrors Linux/Mac where a directory containing read-only + // files can still be recursively deleted. + if (GetLastError() == ERROR_ACCESS_DENIED) { + DWORD attributes = GetFileAttributesW(path()); + if (attributes == INVALID_FILE_ATTRIBUTES) { return false; } - return DeleteFileW(path->AsStringW()) != 0; + if ((attributes & FILE_ATTRIBUTE_READONLY) == FILE_ATTRIBUTE_READONLY) { + attributes &= ~FILE_ATTRIBUTE_READONLY; + + if (SetFileAttributesW(path(), attributes) == 0) { + return false; + } + + return DeleteFileW(path()) != 0; + } } - } - return false; -} - -static bool DeleteDir(const wchar_t* dir_name, PathBuffer* path) { - if ((wcscmp(dir_name, L".") == 0) || (wcscmp(dir_name, L"..") == 0)) { - return true; - } - return path->AddW(dir_name) && DeleteRecursively(path); -} - -static bool DeleteEntry(LPWIN32_FIND_DATAW find_file_data, PathBuffer* path) { - DWORD attributes = find_file_data->dwFileAttributes; - - if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { - return DeleteDir(find_file_data->cFileName, path); - } else { - return DeleteFile(find_file_data->cFileName, path); - } -} - -static bool DeleteRecursively(PathBuffer* path) { - PathBuffer prefixed_path; - if (!prefixed_path.Add(PrefixLongDirectoryPath(path->AsScopedString()))) { return false; } - DWORD attributes = GetFileAttributesW(prefixed_path.AsStringW()); - if (attributes == INVALID_FILE_ATTRIBUTES) { - return false; - } - // If the directory is a junction, it's pointing to some other place in the - // filesystem that we do not want to recurse into. - if ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { - // Just delete the junction itself. - return RemoveDirectoryW(prefixed_path.AsStringW()) != 0; - } - // If it's a file, remove it directly. - if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { - return DeleteFile(L"", &prefixed_path); - } + PathBuffer path_; +}; +} // namespace - if (!prefixed_path.AddW(L"\\*")) { - return false; - } - - WIN32_FIND_DATAW find_file_data; - HANDLE find_handle = - FindFirstFileW(prefixed_path.AsStringW(), &find_file_data); - - if (find_handle == INVALID_HANDLE_VALUE) { - return false; - } - - // Adjust the path by removing the '*' used for the search. - int path_length = prefixed_path.length() - 1; - prefixed_path.Reset(path_length); - - do { - if (!DeleteEntry(&find_file_data, &prefixed_path)) { - break; - } - prefixed_path.Reset(path_length); // DeleteEntry adds to the path. - } while (FindNextFileW(find_handle, &find_file_data) != 0); - - DWORD last_error = GetLastError(); - // Always close handle. - FindClose(find_handle); - if (last_error != ERROR_NO_MORE_FILES) { - // Unexpected error, set and return. - SetLastError(last_error); - return false; - } - // All content deleted successfully, try to delete directory. - prefixed_path.Reset(path_length - - 1); // Drop the "\" from the end of the path. - return RemoveDirectoryW(prefixed_path.AsStringW()) != 0; -} - -static Directory::ExistsResult ExistsHelper(const wchar_t* dir_name) { +Directory::ExistsResult Directory::Exists(const wchar_t* dir_name) { DWORD attributes = GetFileAttributesW(dir_name); if (attributes == INVALID_FILE_ATTRIBUTES) { DWORD last_error = GetLastError(); @@ -356,9 +379,8 @@ static Directory::ExistsResult ExistsHelper(const wchar_t* dir_name) { Directory::ExistsResult Directory::Exists(Namespace* namespc, const char* dir_name) { - const char* prefixed_dir_name = PrefixLongDirectoryPath(dir_name); - Utf8ToWideScope system_name(prefixed_dir_name); - return ExistsHelper(system_name.wide()); + const auto path = ToWinAPIDirectoryPath(dir_name); + return Exists(path.get()); } char* Directory::CurrentNoScope() { @@ -378,12 +400,11 @@ char* Directory::CurrentNoScope() { } bool Directory::Create(Namespace* namespc, const char* dir_name) { - const char* prefixed_dir_name = PrefixLongDirectoryPath(dir_name); - Utf8ToWideScope system_name(prefixed_dir_name); - int create_status = CreateDirectoryW(system_name.wide(), nullptr); + const auto path = ToWinAPIDirectoryPath(dir_name); + int create_status = CreateDirectoryW(path.get(), nullptr); // If the directory already existed, treat it as a success. if ((create_status == 0) && (GetLastError() == ERROR_ALREADY_EXISTS) && - (ExistsHelper(system_name.wide()) == EXISTS)) { + (Exists(path.get()) == EXISTS)) { return true; } return (create_status != 0); @@ -399,8 +420,8 @@ const char* Directory::SystemTemp(Namespace* namespc) { // Creates a new temporary directory with a UUID as suffix. static const char* CreateTempFromUUID(const char* prefix) { PathBuffer path; - Utf8ToWideScope system_prefix(prefix); - if (!path.AddW(system_prefix.wide())) { + const auto system_prefix = Utf8ToWideChar(prefix); + if (!path.AddW(system_prefix.get())) { return nullptr; } @@ -414,14 +435,14 @@ static const char* CreateTempFromUUID(const char* prefix) { if ((status != RPC_S_OK) && (status != RPC_S_UUID_LOCAL_ONLY)) { return nullptr; } - RPC_WSTR uuid_string; + wchar_t* uuid_string; status = UuidToStringW(&uuid, &uuid_string); if (status != RPC_S_OK) { return nullptr; } // RPC_WSTR is an unsigned short*, so we cast to wchar_t*. - if (!path.AddW(reinterpret_cast(uuid_string))) { + if (!path.AddW(uuid_string)) { return nullptr; } RpcStringFreeW(&uuid_string); @@ -445,8 +466,8 @@ static const char* CreateTempFromUUID(const char* prefix) { // to have a small bound on the number of calls to CreateDirectoryW(). const char* Directory::CreateTemp(Namespace* namespc, const char* prefix) { PathBuffer path; - Utf8ToWideScope system_prefix(prefix); - if (!path.AddW(system_prefix.wide())) { + const auto system_prefix = Utf8ToWideChar(prefix); + if (!path.AddW(system_prefix.get())) { return nullptr; } @@ -485,39 +506,35 @@ const char* Directory::CreateTemp(Namespace* namespc, const char* prefix) { bool Directory::Delete(Namespace* namespc, const char* dir_name, bool recursive) { - const char* prefixed_dir_name = PrefixLongDirectoryPath(dir_name); + const auto path = + ToWinAPIDirectoryPath(dir_name, /*force_long_prefix=*/recursive); bool result = false; - Utf8ToWideScope system_dir_name(prefixed_dir_name); if (!recursive) { - if (File::GetType(namespc, prefixed_dir_name, true) == File::kIsDirectory) { - result = (RemoveDirectoryW(system_dir_name.wide()) != 0); + if (File::GetType(path.get(), /*follow_links=*/true) == + File::kIsDirectory) { + result = (RemoveDirectoryW(path.get()) != 0); } else { SetLastError(ERROR_FILE_NOT_FOUND); } } else { - PathBuffer path; - if (path.AddW(system_dir_name.wide())) { - result = DeleteRecursively(&path); - } + RecursiveDeleter deleter; + result = deleter.DeleteRecursively(path); } return result; } bool Directory::Rename(Namespace* namespc, - const char* path, - const char* new_path) { - const char* prefixed_dir = PrefixLongDirectoryPath(path); - Utf8ToWideScope system_path(prefixed_dir); - ExistsResult exists = ExistsHelper(system_path.wide()); + const char* old_name, + const char* new_name) { + const auto old_path = ToWinAPIDirectoryPath(old_name); + ExistsResult exists = Exists(old_path.get()); if (exists != EXISTS) { SetLastError(ERROR_FILE_NOT_FOUND); return false; } - const char* prefixed_new_dir = PrefixLongDirectoryPath(new_path); - Utf8ToWideScope system_new_path(prefixed_new_dir); + const auto new_path = ToWinAPIDirectoryPath(new_name); DWORD flags = MOVEFILE_WRITE_THROUGH; - int move_status = - MoveFileExW(system_path.wide(), system_new_path.wide(), flags); + int move_status = MoveFileExW(old_path.get(), new_path.get(), flags); return (move_status != 0); } diff --git a/runtime/bin/file.h b/runtime/bin/file.h index 6c217a8ac5f..237d424fd9e 100644 --- a/runtime/bin/file.h +++ b/runtime/bin/file.h @@ -269,6 +269,9 @@ class File : public ReferenceCounted { static bool IsAbsolutePath(const char* path); static const char* PathSeparator(); static const char* StringEscapedPathSeparator(); +#if defined(DART_HOST_OS_WINDOWS) + static Type GetType(const wchar_t* path, bool follow_links); +#endif static Type GetType(Namespace* namespc, const char* path, bool follow_links); static Identical AreIdentical(Namespace* namespc_1, const char* file_1, diff --git a/runtime/bin/file_system_watcher_win.cc b/runtime/bin/file_system_watcher_win.cc index 84a4c82a2ea..9ba9f687c54 100644 --- a/runtime/bin/file_system_watcher_win.cc +++ b/runtime/bin/file_system_watcher_win.cc @@ -36,9 +36,9 @@ intptr_t FileSystemWatcher::WatchPath(intptr_t id, int events, bool recursive) { USE(id); - Utf8ToWideScope name(path); + const auto name = Utf8ToWideChar(path); HANDLE dir = - CreateFileW(name.wide(), FILE_LIST_DIRECTORY, + CreateFileW(name.get(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr); diff --git a/runtime/bin/file_win.cc b/runtime/bin/file_win.cc index a6c608bbbb8..d1132fd681b 100644 --- a/runtime/bin/file_win.cc +++ b/runtime/bin/file_win.cc @@ -7,7 +7,7 @@ #include #include -#include +#include #include // NOLINT #include // NOLINT @@ -322,153 +322,126 @@ File* File::OpenFD(int fd) { return new File(new FileHandle(fd)); } -class StringRAII { - public: - StringRAII(StringRAII& origin) { - own_ = origin.own_; - s_ = origin.release(); - } - - explicit StringRAII(const char* s) : own_(false), s_(s) {} - explicit StringRAII(char* s) : own_(true), s_(s) {} - ~StringRAII() { - if (own_) { - free(const_cast(s_)); +static std::unique_ptr ConvertToAbsolutePath( + const std::unique_ptr& path) { + // Initial buffer size is selected to avoid overallocating too much + // memory. + int buffer_size = 1024; + do { + auto buffer = std::make_unique(buffer_size); + int full_path_length = + GetFullPathNameW(path.get(), buffer_size, buffer.get(), + /*lpFilePart=*/nullptr); + if (full_path_length == 0) { + return nullptr; } - } - const char* str() const { return s_; } - const char* release() { - own_ = false; - return s_; - } - private: - bool own_; - const char* s_; -}; + // Note: when sucessful full_path_length does *not* include terminating + // NUL character, but on failure it *does* include it when returning + // the size of buffer which we need. Hence comparison here is `<`, rather + // than `<=`. + if (full_path_length < buffer_size) { + return buffer; + } -class Wchart { - public: - explicit Wchart(int size) { - buf_ = reinterpret_cast(malloc(size * sizeof(wchar_t))); - } - ~Wchart() { free(buf_); } - wchar_t* buf() const { return buf_; } - - private: - wchar_t* buf_; -}; - -static StringRAII ConvertToAbsolutePath(const char* path, - bool* p_has_converted_successfully) { - const int kPathLength = 16384; - Wchart buffer(kPathLength); // use some reasonably large initial buffer - Utf8ToWideScope path_utf8_to_wide(path); - *p_has_converted_successfully = true; - int full_path_length = - GetFullPathNameW(path_utf8_to_wide.wide(), kPathLength, buffer.buf(), - /*lpFilePart=*/nullptr); - if (full_path_length == 0) { - *p_has_converted_successfully = false; - // GetFullPathNameW failed - return StringRAII(path); - } - if (full_path_length < kPathLength) { - WideToUtf8Scope scope(buffer.buf()); - return StringRAII(Utils::StrDup(scope.utf8())); - } - - // Try again with bigger buffer. - Wchart bigger_buffer(full_path_length); - if (GetFullPathNameW(path_utf8_to_wide.wide(), full_path_length, - bigger_buffer.buf(), - /*lpFilePart=*/nullptr) == 0) { - *p_has_converted_successfully = false; - // GetFullPathNameW failed - return StringRAII(path); - } - WideToUtf8Scope scope(bigger_buffer.buf()); - return StringRAII(Utils::StrDup(scope.utf8())); + buffer_size = full_path_length; + } while (true); } -static StringRAII PrefixLongPathIfExceedLimit( - const char* path, - bool is_file, - std::function allocate) { +static bool IsAbsolutePath(const wchar_t* pathname) { + if (pathname == nullptr) return false; + char first = pathname[0]; + char second = pathname[1]; + if (first == L'\\' && second == L'\\') return true; + if (second != L':') return false; + first |= 0x20; + char third = pathname[2]; + return (first >= L'a') && (first <= L'z') && + (third == L'\\' || third == L'/'); +} + +// Converts the given UTF8 path to wide char. If resulting path does not +// fit into MAX_PATH / MAX_DIRECTORY_PATH (or if |force_long_prefix| is true) +// then converts the path to the absolute `\\?\`-prefixed form. +// +// Note: +// 1. Some WinAPI functions (like SetCurrentDirectoryW) are always limited +// to MAX_PATH long paths and converting to `\\?\`-prefixed form does not +// remove this limitation. Always check Win API documentation. +// 2. This function might change relative path to an absolute path. +static std::unique_ptr ToWinAPIPath(const char* utf8_path, + bool is_file, + bool force_long_prefix) { + auto path = Utf8ToWideChar(utf8_path); + // File name and Directory name have different size limit. // Reference: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation const int path_short_limit = is_file ? MAX_PATH : MAX_DIRECTORY_PATH; - const char* kLongPathPrefix = "\\\\?\\"; + const wchar_t* kLongPathPrefix = L"\\\\?\\"; const int kLongPathPrefixLength = 4; - // if absolute path is short or already prefixed, just return it. - if ((File::IsAbsolutePath(path) && strlen(path) < path_short_limit) || - strncmp(path, kLongPathPrefix, kLongPathPrefixLength) == 0) { - return StringRAII(path); + std::unique_ptr absolute_path; + if (!IsAbsolutePath(path.get())) { + absolute_path = ConvertToAbsolutePath(path); + if (absolute_path == nullptr) { + return path; + } + } else { + absolute_path = std::move(path); } - // Long relative path have to be converted to absolute path before prefixing. - bool is_ok = true; - StringRAII absolute_path_raii( - File::IsAbsolutePath(path) - ? StringRAII(path) - : StringRAII(ConvertToAbsolutePath(path, &is_ok))); - if (!is_ok) { - return StringRAII(path); + int path_length = wcslen(absolute_path.get()); + + if (!force_long_prefix && path_length < path_short_limit) { + if (path == nullptr) { + return absolute_path; + } else { + return path; + } } - const char* absolute_path = absolute_path_raii.str(); - int length = strlen(absolute_path); - if (length < path_short_limit) { - // No need for a prefix if absolute path is short - return StringRAII(path); - } - if (strncmp(absolute_path, kLongPathPrefix, kLongPathPrefixLength) == 0) { - // Relative path converted to absolute could get a prefix. - return StringRAII(absolute_path); + + if (wcsncmp(absolute_path.get(), kLongPathPrefix, kLongPathPrefixLength) == + 0) { + return absolute_path; } // Add prefix and replace forward slashes with backward slashes. - char* result = allocate((kLongPathPrefixLength + length + 1) * sizeof(char)); - strncpy(result, kLongPathPrefix, kLongPathPrefixLength); - for (int i = 0; i < length; i++) { - result[kLongPathPrefixLength + i] = - absolute_path[i] == '/' ? '\\' : absolute_path[i]; + auto result = + std::make_unique(kLongPathPrefixLength + path_length + 1); + wcsncpy(result.get(), kLongPathPrefix, kLongPathPrefixLength); + for (int i = 0; i < path_length; i++) { + result.get()[kLongPathPrefixLength + i] = + absolute_path[i] == L'/' ? L'\\' : absolute_path[i]; } - result[length + kLongPathPrefixLength] = '\0'; - return StringRAII(result); + result.get()[path_length + kLongPathPrefixLength] = L'\0'; + return result; } -static const char* PrefixLongFilePath(const char* path) { - return PrefixLongPathIfExceedLimit( - path, /*is_file=*/true, - [](int size) { - return reinterpret_cast(Dart_ScopeAllocate(size)); - }) - .release(); +// Converts the given UTF8 path to wide char. If resulting path does not +// fit into MAX_DIRECTORY_PATH (or if |force_long_prefix| is true) then +// converts the path to the absolute `\\?\`-prefixed form. +// +// Note: +// 1. Some WinAPI functions (like SetCurrentDirectoryW) are always limited +// to MAX_PATH long paths and converting to `\\?\`-prefixed form does not +// remove this limitation. Always check Win API documentation. +// 2. This function might change relative path to an absolute path. +static std::unique_ptr ToWinAPIFilePath( + const char* path, + bool force_long_prefix = false) { + return ToWinAPIPath(path, /*is_file=*/true, force_long_prefix); } -static StringRAII PrefixLongFilePathNoScope(const char* path) { - return PrefixLongPathIfExceedLimit(path, /*is_file=*/true, [](int size) { - return reinterpret_cast(malloc(size)); - }); +std::unique_ptr ToWinAPIDirectoryPath( + const char* path, + bool force_long_prefix /* = false */) { + return ToWinAPIPath(path, /*is_file=*/false, force_long_prefix); } -const char* PrefixLongDirectoryPath(const char* path) { - return PrefixLongPathIfExceedLimit( - path, /*is_file=*/false, - [](int size) { - return reinterpret_cast(Dart_ScopeAllocate(size)); - }) - .release(); -} - -File* File::Open(Namespace* namespc, const char* path, FileOpenMode mode) { - // File::Open can be called without scope(when launching isolate), - // so it mallocs prefixed path - StringRAII string_raii = PrefixLongFilePathNoScope(path); - Utf8ToWideScope system_name(string_raii.str()); - File* file = FileOpenW(system_name.wide(), mode); +File* File::Open(Namespace* namespc, const char* name, FileOpenMode mode) { + const auto path = ToWinAPIFilePath(name); + File* file = FileOpenW(path.get(), mode); return file; } @@ -479,13 +452,13 @@ Utils::CStringUniquePtr File::UriToPath(const char* uri) { return Utils::CreateCStringUniquePtr(nullptr); } - Utf8ToWideScope uri_w(uri_decoder.decoded()); - if (!UrlIsFileUrlW(uri_w.wide())) { + const auto uri_w = Utf8ToWideChar(uri_decoder.decoded()); + if (!UrlIsFileUrlW(uri_w.get())) { return Utils::CreateCStringUniquePtr(Utils::StrDup(uri_decoder.decoded())); } wchar_t filename_w[MAX_PATH]; DWORD filename_len = MAX_PATH; - HRESULT result = PathCreateFromUrlW(uri_w.wide(), filename_w, &filename_len, + HRESULT result = PathCreateFromUrlW(uri_w.get(), filename_w, &filename_len, /* dwFlags= */ 0); if (result != S_OK) { return Utils::CreateCStringUniquePtr(nullptr); @@ -519,7 +492,7 @@ File* File::OpenStdio(int fd) { return new File(new FileHandle(stdio_fd)); } -static bool StatHelper(wchar_t* path, struct __stat64* st) { +static bool StatHelper(const wchar_t* path, struct __stat64* st) { int stat_status = _wstat64(path, st); if (stat_status != 0) { return false; @@ -531,11 +504,14 @@ static bool StatHelper(wchar_t* path, struct __stat64* st) { return true; } -bool File::Exists(Namespace* namespc, const char* name) { - StringRAII string_raii = PrefixLongFilePathNoScope(name); - Utf8ToWideScope system_name(string_raii.str()); +static bool FileExists(const wchar_t* path) { struct __stat64 st; - return StatHelper(system_name.wide(), &st); + return StatHelper(path, &st); +} + +bool File::Exists(Namespace* namespc, const char* name) { + const auto path = ToWinAPIFilePath(name); + return FileExists(path.get()); } bool File::ExistsUri(Namespace* namespc, const char* uri) { @@ -548,12 +524,12 @@ bool File::ExistsUri(Namespace* namespc, const char* uri) { } bool File::Create(Namespace* namespc, const char* name, bool exclusive) { - Utf8ToWideScope system_name(PrefixLongFilePath(name)); + const auto path = ToWinAPIFilePath(name); int flags = O_RDONLY | O_CREAT; if (exclusive) { flags |= O_EXCL; } - int fd = _wopen(system_name.wide(), flags, 0666); + int fd = _wopen(path.get(), flags, 0666); if (fd < 0) { return false; } @@ -593,13 +569,14 @@ typedef struct _REPARSE_DATA_BUFFER { bool File::CreateLink(Namespace* namespc, const char* utf8_name, const char* utf8_target) { - Utf8ToWideScope name(PrefixLongFilePath(utf8_name)); - Utf8ToWideScope target(PrefixLongFilePath(utf8_target)); - DWORD flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; + const auto name = ToWinAPIFilePath(utf8_name); - File::Type type; + std::unique_ptr target; + bool target_is_directory; if (File::IsAbsolutePath(utf8_target)) { - type = File::GetType(namespc, utf8_target, true); + target = ToWinAPIFilePath(utf8_target); + target_is_directory = + File::GetType(target.get(), /*follow_links=*/true) == kIsDirectory; } else { // The path of `target` is relative to `name`. // @@ -618,15 +595,17 @@ bool File::CreateLink(Namespace* namespc, // 1. target_path := name ..\..\Link // 2. target_path := remove_file(target_path) ..\..\ // 3. target_path := combine(target_path, target) ..\..\..\Dir\MyFile + target = Utf8ToWideChar(utf8_target); // 1. target_path := name - intptr_t target_path_max_length = name.length() + target.length(); - Wchart target_path(target_path_max_length); - wcscpy_s(target_path.buf(), target_path_max_length, name.wide()); + intptr_t target_path_max_length = + wcslen(name.get()) + wcslen(target.get()) + 2; + auto target_path = std::make_unique(target_path_max_length); + wcscpy_s(target_path.get(), target_path_max_length, name.get()); // 2. target_path := remove_file(target_path) HRESULT remove_result = - PathCchRemoveFileSpec(target_path.buf(), target_path_max_length); + PathCchRemoveFileSpec(target_path.get(), target_path_max_length); if (remove_result == S_FALSE) { // If the file component could not be removed, then `name` is // top-level, like "C:\" or "/". Attempts to create files at those paths @@ -640,27 +619,29 @@ bool File::CreateLink(Namespace* namespc, // 3. target_path := combine(target_path, target) HRESULT combine_result = PathCchCombineEx( - target_path.buf(), target_path_max_length, target_path.buf(), - target.wide(), PATHCCH_ALLOW_LONG_PATHS); + target_path.get(), target_path_max_length, target_path.get(), + target.get(), PATHCCH_ALLOW_LONG_PATHS); if (combine_result != S_OK) { SetLastError(combine_result); return false; } - type = PathIsDirectoryW(target_path.buf()) ? kIsDirectory : kIsFile; + target_is_directory = + File::GetType(target_path.get(), /*follow_links=*/true) == kIsDirectory; } - if (type == kIsDirectory) { + DWORD flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; + if (target_is_directory) { flags |= SYMBOLIC_LINK_FLAG_DIRECTORY; } - int create_status = CreateSymbolicLinkW(name.wide(), target.wide(), flags); + int create_status = CreateSymbolicLinkW(name.get(), target.get(), flags); // If running on a Windows 10 build older than 14972, an invalid parameter // error will be returned when trying to use the // SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE flag. Retry without the flag. if ((create_status == 0) && (GetLastError() == ERROR_INVALID_PARAMETER)) { flags &= ~SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; - create_status = CreateSymbolicLinkW(name.wide(), target.wide(), flags); + create_status = CreateSymbolicLinkW(name.get(), target.get(), flags); } return (create_status != 0); @@ -678,15 +659,14 @@ bool File::CreatePipe(Namespace* namespc, File** readPipe, File** writePipe) { } bool File::Delete(Namespace* namespc, const char* name) { - Utf8ToWideScope system_name(PrefixLongFilePath(name)); - int status = _wremove(system_name.wide()); + const auto path = ToWinAPIFilePath(name); + int status = _wremove(path.get()); return status != -1; } -bool File::DeleteLink(Namespace* namespc, const char* name) { - Utf8ToWideScope system_name(PrefixLongFilePath(name)); +static bool DeleteLinkHelper(const wchar_t* path) { bool result = false; - DWORD attributes = GetFileAttributesW(system_name.wide()); + DWORD attributes = GetFileAttributesW(path); if ((attributes == INVALID_FILE_ATTRIBUTES) || ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0)) { SetLastError(ERROR_NOT_A_REPARSE_POINT); @@ -695,207 +675,176 @@ bool File::DeleteLink(Namespace* namespc, const char* name) { if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { // It's a junction, which is a special type of directory, or a symbolic // link to a directory. Remove the directory. - result = (RemoveDirectoryW(system_name.wide()) != 0); + result = (RemoveDirectoryW(path) != 0); } else { // Symbolic link to a file. Remove the file. - result = (DeleteFileW(system_name.wide()) != 0); + result = (DeleteFileW(path) != 0); } return result; } -bool File::Rename(Namespace* namespc, - const char* old_path, - const char* new_path) { - const char* prefixed_old_path = PrefixLongFilePath(old_path); - File::Type type = GetType(namespc, prefixed_old_path, false); - if (type != kIsFile) { +bool File::DeleteLink(Namespace* namespc, const char* name) { + const auto path = ToWinAPIFilePath(name); + return DeleteLinkHelper(path.get()); +} + +static bool RenameHelper(File::Type expected, + const char* old_name, + const char* new_name) { + const auto old_path = ToWinAPIFilePath(old_name); + File::Type type = File::GetType(old_path.get(), /*follow_links=*/false); + if (type != expected) { SetLastError(ERROR_FILE_NOT_FOUND); return false; } - const char* prefixed_new_path = PrefixLongFilePath(new_path); - Utf8ToWideScope system_old_path(prefixed_old_path); - Utf8ToWideScope system_new_path(prefixed_new_path); + const auto new_path = ToWinAPIFilePath(new_name); DWORD flags = MOVEFILE_WRITE_THROUGH | MOVEFILE_REPLACE_EXISTING; // Symbolic links (e.g. produced by Link.create) to directories on Windows // appear as special directories. MoveFileExW's MOVEFILE_REPLACE_EXISTING // does not allow for replacement of directories, so we need to remove it // before renaming. - if ((Directory::Exists(namespc, prefixed_new_path) == Directory::EXISTS) && - (GetType(namespc, prefixed_new_path, false) == kIsLink)) { + if ((Directory::Exists(new_path.get()) == Directory::EXISTS) && + (File::GetType(new_path.get(), /*follow_links=*/false) == + File::kIsLink)) { // Bail out if the DeleteLink call fails. - if (!DeleteLink(namespc, prefixed_new_path)) { + if (!DeleteLinkHelper(new_path.get())) { return false; } } - int move_status = - MoveFileExW(system_old_path.wide(), system_new_path.wide(), flags); + int move_status = MoveFileExW(old_path.get(), new_path.get(), flags); return (move_status != 0); } +bool File::Rename(Namespace* namespc, + const char* old_name, + const char* new_name) { + return RenameHelper(File::kIsFile, old_name, new_name); +} + bool File::RenameLink(Namespace* namespc, - const char* old_path, - const char* new_path) { - const char* prefixed_old_path = PrefixLongFilePath(old_path); - File::Type type = GetType(namespc, prefixed_old_path, false); - if (type != kIsLink) { - SetLastError(ERROR_FILE_NOT_FOUND); - return false; - } - Utf8ToWideScope system_old_path(prefixed_old_path); - const char* prefixed_new_path = PrefixLongFilePath(new_path); - Utf8ToWideScope system_new_path(prefixed_new_path); - DWORD flags = MOVEFILE_WRITE_THROUGH | MOVEFILE_REPLACE_EXISTING; - - // Symbolic links (e.g. produced by Link.create) to directories on Windows - // appear as special directories. MoveFileExW's MOVEFILE_REPLACE_EXISTING - // does not allow for replacement of directories, so we need to remove it - // before renaming. - if ((Directory::Exists(namespc, prefixed_new_path) == Directory::EXISTS) && - (GetType(namespc, prefixed_new_path, false) == kIsLink)) { - // Bail out if the DeleteLink call fails. - if (!DeleteLink(namespc, prefixed_new_path)) { - return false; - } - } - int move_status = - MoveFileExW(system_old_path.wide(), system_new_path.wide(), flags); - return (move_status != 0); + const char* old_name, + const char* new_name) { + return RenameHelper(File::kIsLink, old_name, new_name); } -static wchar_t* CopyToDartScopeString(wchar_t* string) { - wchar_t* wide_path = reinterpret_cast( - Dart_ScopeAllocate(MAX_PATH * sizeof(wchar_t) + 1)); - wcscpy(wide_path, string); - return wide_path; -} - -static wchar_t* CopyIntoTempFile(const char* src, const char* dest) { - // This function will copy the file to a temp file in the destination - // directory and return the path of temp file. - // Creating temp file name has the same logic as Directory::CreateTemp(), - // which tries with the rng and falls back to a uuid if it failed. - const char* last_back_slash = strrchr(dest, '\\'); - // It is possible the path uses forwardslash as path separator. - const char* last_forward_slash = strrchr(dest, '/'); - const char* last_path_separator = nullptr; - if (last_back_slash == nullptr && last_forward_slash == nullptr) { - return nullptr; - } else if (last_forward_slash != nullptr && last_forward_slash != nullptr) { - // If both types occur in the path, use the one closer to the end. - if (last_back_slash - dest > last_forward_slash - dest) { - last_path_separator = last_back_slash; - } else { - last_path_separator = last_forward_slash; +static std::unique_ptr GetDirectoryPath( + const std::unique_ptr& path) { + for (intptr_t i = wcslen(path.get()) - 1; i >= 0; --i) { + if (path.get()[i] == '\\' || path.get()[i] == '/') { + auto result = std::make_unique(i + 1); + wcsncpy(result.get(), path.get(), i); + return result; } - } else { - last_path_separator = - (last_forward_slash == nullptr) ? last_back_slash : last_forward_slash; - } - int length_of_parent_dir = last_path_separator - dest + 1; - if (length_of_parent_dir + 8 > MAX_PATH) { - return nullptr; - } - uint32_t suffix_bytes = 0; - const int kSuffixSize = sizeof(suffix_bytes); - if (Crypto::GetRandomBytes(kSuffixSize, - reinterpret_cast(&suffix_bytes))) { - PathBuffer buffer; - char* dir = reinterpret_cast( - Dart_ScopeAllocate(1 + sizeof(char) * length_of_parent_dir)); - memmove(dir, dest, length_of_parent_dir); - dir[length_of_parent_dir] = '\0'; - if (!buffer.Add(dir)) { - return nullptr; - } - - char suffix[8 + 1]; - Utils::SNPrint(suffix, sizeof(suffix), "%x", suffix_bytes); - Utf8ToWideScope source_path(src); - if (!buffer.Add(suffix)) { - return nullptr; - } - if (CopyFileExW(source_path.wide(), buffer.AsStringW(), nullptr, nullptr, - nullptr, 0) != 0) { - return CopyToDartScopeString(buffer.AsStringW()); - } - // If CopyFileExW() fails to copy to a temp file with random hex, fall - // back to copy to a uuid temp file. - } - // UUID has a total of 36 characters in the form of - // xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx. - if (length_of_parent_dir + 36 > MAX_PATH) { - return nullptr; - } - UUID uuid; - RPC_STATUS status = UuidCreateSequential(&uuid); - if ((status != RPC_S_OK) && (status != RPC_S_UUID_LOCAL_ONLY)) { - return nullptr; - } - RPC_WSTR uuid_string; - status = UuidToStringW(&uuid, &uuid_string); - if (status != RPC_S_OK) { - return nullptr; - } - PathBuffer buffer; - char* dir = reinterpret_cast( - Dart_ScopeAllocate(1 + sizeof(char) * length_of_parent_dir)); - memmove(dir, dest, length_of_parent_dir); - dir[length_of_parent_dir] = '\0'; - Utf8ToWideScope dest_path(dir); - if (!buffer.AddW(dest_path.wide()) || - !buffer.AddW(reinterpret_cast(uuid_string))) { - return nullptr; - } - - RpcStringFreeW(&uuid_string); - Utf8ToWideScope source_path(src); - if (CopyFileExW(source_path.wide(), buffer.AsStringW(), nullptr, nullptr, - nullptr, 0) != 0) { - return CopyToDartScopeString(buffer.AsStringW()); } return nullptr; } +static void FreeUUID(wchar_t* ptr) { + RpcStringFreeW(&ptr); +} + +static std::unique_ptr GenerateUUIDString() { + UUID uuid; + RPC_STATUS status = UuidCreateSequential(&uuid); + if ((status != RPC_S_OK) && (status != RPC_S_UUID_LOCAL_ONLY)) { + return {nullptr, nullptr}; + } + wchar_t* uuid_string; + status = UuidToStringW(&uuid, &uuid_string); + if (status != RPC_S_OK) { + return {nullptr, nullptr}; + } + + return {uuid_string, &FreeUUID}; +} + +// This function will copy the |src| file to a temporary file in the +// directory where |dest| resides and returns the path of temp file. +static std::unique_ptr CopyIntoTempFile( + const std::unique_ptr& src, + const std::unique_ptr& dest) { + const auto dir = GetDirectoryPath(dest); + if (dir == nullptr) { + return nullptr; + } + + uint32_t suffix_bytes = 0; + const int kSuffixSize = sizeof(suffix_bytes); + if (Crypto::GetRandomBytes(kSuffixSize, + reinterpret_cast(&suffix_bytes))) { + const size_t file_path_buf_size = wcslen(dir.get()) + 8 + 1; + auto file_path = std::make_unique(file_path_buf_size); + swprintf(file_path.get(), file_path_buf_size, L"%s%x", dir.get(), + suffix_bytes); + + if (CopyFileExW(src.get(), file_path.get(), nullptr, nullptr, nullptr, 0) != + 0) { + return file_path; + } + + // If CopyFileExW() fails to copy to a temp file with random hex, fall + // back to copy to a uuid temp file. + } + + const auto uuid_str = GenerateUUIDString(); + if (uuid_str == nullptr) { + return nullptr; + } + + const size_t file_path_buf_size = + wcslen(dir.get()) + wcslen(uuid_str.get()) + 1; + auto file_path = std::make_unique(file_path_buf_size); + swprintf(file_path.get(), file_path_buf_size, L"%s%s", dir.get(), + uuid_str.get()); + + if (CopyFileExW(src.get(), file_path.get(), nullptr, nullptr, nullptr, 0) != + 0) { + return file_path; + } + + return nullptr; +} + bool File::Copy(Namespace* namespc, - const char* old_path, - const char* new_path) { - const char* prefixed_old_path = PrefixLongFilePath(old_path); - const char* prefixed_new_path = PrefixLongFilePath(new_path); - File::Type type = GetType(namespc, prefixed_old_path, false); + const char* old_name, + const char* new_name) { + // We are going to concatenate old path with temporary file names in + // CopyIntoTempFile so we force long prefix no matter what. + const auto old_path = ToWinAPIFilePath(old_name, /*force_long_prefix=*/true); + const auto new_path = ToWinAPIFilePath(new_name); + File::Type type = GetType(old_path.get(), /*follow_links=*/false); if (type != kIsFile) { SetLastError(ERROR_FILE_NOT_FOUND); return false; } - wchar_t* temp_file = CopyIntoTempFile(prefixed_old_path, prefixed_new_path); + const auto temp_file = CopyIntoTempFile(old_path, new_path); if (temp_file == nullptr) { // If temp file creation fails, fall back on doing a direct copy. - Utf8ToWideScope system_old_path(prefixed_old_path); - Utf8ToWideScope system_new_path(prefixed_new_path); - return CopyFileExW(system_old_path.wide(), system_new_path.wide(), nullptr, - nullptr, nullptr, 0) != 0; + return CopyFileExW(old_path.get(), new_path.get(), nullptr, nullptr, + nullptr, 0) != 0; } - Utf8ToWideScope system_new_dest(prefixed_new_path); // Remove the existing file. Otherwise, renaming will fail. - if (Exists(namespc, prefixed_new_path)) { - DeleteFileW(system_new_dest.wide()); + if (FileExists(new_path.get())) { + DeleteFileW(new_path.get()); } - if (!MoveFileW(temp_file, system_new_dest.wide())) { + if (!MoveFileW(temp_file.get(), new_path.get())) { DWORD error = GetLastError(); - DeleteFileW(temp_file); + DeleteFileW(temp_file.get()); SetLastError(error); return false; } + return true; } int64_t File::LengthFromPath(Namespace* namespc, const char* name) { struct __stat64 st; - Utf8ToWideScope system_name(PrefixLongFilePath(name)); - if (!StatHelper(system_name.wide(), &st)) { + const auto path = ToWinAPIFilePath(name); + if (!StatHelper(path.get(), &st)) { return -1; } return st.st_size; @@ -905,10 +854,9 @@ const char* File::LinkTarget(Namespace* namespc, const char* pathname, char* dest, int dest_size) { - const wchar_t* name = - StringUtilsWin::Utf8ToWide(PrefixLongFilePath(pathname)); + const auto path = ToWinAPIFilePath(pathname); HANDLE dir_handle = CreateFileW( - name, GENERIC_READ, + path.get(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); @@ -993,13 +941,12 @@ const char* File::LinkTarget(Namespace* namespc, } void File::Stat(Namespace* namespc, const char* name, int64_t* data) { - const char* prefixed_name = PrefixLongFilePath(name); - File::Type type = GetType(namespc, prefixed_name, true); + const auto path = ToWinAPIFilePath(name); + File::Type type = GetType(path.get(), /*follow_links=*/true); data[kType] = type; if (type != kDoesNotExist) { struct _stat64 st; - Utf8ToWideScope system_name(prefixed_name); - int stat_status = _wstat64(system_name.wide(), &st); + int stat_status = _wstat64(path.get(), &st); if (stat_status == 0) { data[kCreatedTime] = st.st_ctime * 1000; data[kModifiedTime] = st.st_mtime * 1000; @@ -1014,8 +961,8 @@ void File::Stat(Namespace* namespc, const char* name, int64_t* data) { time_t File::LastAccessed(Namespace* namespc, const char* name) { struct __stat64 st; - Utf8ToWideScope system_name(PrefixLongFilePath(name)); - if (!StatHelper(system_name.wide(), &st)) { + const auto path = ToWinAPIFilePath(name); + if (!StatHelper(path.get(), &st)) { return -1; } return st.st_atime; @@ -1023,8 +970,8 @@ time_t File::LastAccessed(Namespace* namespc, const char* name) { time_t File::LastModified(Namespace* namespc, const char* name) { struct __stat64 st; - Utf8ToWideScope system_name(PrefixLongFilePath(name)); - if (!StatHelper(system_name.wide(), &st)) { + const auto path = ToWinAPIFilePath(name); + if (!StatHelper(path.get(), &st)) { return -1; } return st.st_mtime; @@ -1034,8 +981,8 @@ bool File::SetLastAccessed(Namespace* namespc, const char* name, int64_t millis) { struct __stat64 st; - Utf8ToWideScope system_name(PrefixLongFilePath(name)); - if (!StatHelper(system_name.wide(), &st)) { // Checks that it is a file. + const auto path = ToWinAPIFilePath(name); + if (!StatHelper(path.get(), &st)) { // Checks that it is a file. return false; } @@ -1047,7 +994,7 @@ bool File::SetLastAccessed(Namespace* namespc, // So set the file access time directly using SetFileTime. FILETIME at = GetFiletimeFromMillis(millis); HANDLE file_handle = - CreateFileW(system_name.wide(), FILE_WRITE_ATTRIBUTES, + CreateFileW(path.get(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (file_handle == INVALID_HANDLE_VALUE) { @@ -1063,8 +1010,8 @@ bool File::SetLastModified(Namespace* namespc, int64_t millis) { // First get the current times. struct __stat64 st; - Utf8ToWideScope system_name(PrefixLongFilePath(name)); - if (!StatHelper(system_name.wide(), &st)) { + const auto path = ToWinAPIFilePath(name); + if (!StatHelper(path.get(), &st)) { return false; } @@ -1072,7 +1019,7 @@ bool File::SetLastModified(Namespace* namespc, struct __utimbuf64 times; times.actime = st.st_atime; times.modtime = millis / kMillisecondsPerSecond; - return _wutime64(system_name.wide(), ×) == 0; + return _wutime64(path.get(), ×) == 0; } // Keep this function synchronized with the behavior @@ -1092,10 +1039,10 @@ const char* File::GetCanonicalPath(Namespace* namespc, const char* pathname, char* dest, int dest_size) { - Utf8ToWideScope system_name(PrefixLongFilePath(pathname)); + const auto path = ToWinAPIFilePath(pathname); HANDLE file_handle = - CreateFileW(system_name.wide(), 0, FILE_SHARE_READ, nullptr, - OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + CreateFileW(path.get(), 0, FILE_SHARE_READ, nullptr, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (file_handle == INVALID_HANDLE_VALUE) { return nullptr; } @@ -1108,8 +1055,9 @@ const char* File::GetCanonicalPath(Namespace* namespc, SetLastError(error); return nullptr; } - auto path = std::unique_ptr(new wchar_t[required_size]); - int result_size = GetFinalPathNameByHandle(file_handle, path.get(), + + const auto canonical_path = std::make_unique(required_size); + int result_size = GetFinalPathNameByHandle(file_handle, canonical_path.get(), required_size, VOLUME_NAME_DOS); ASSERT(result_size <= required_size - 1); CloseHandle(file_handle); @@ -1117,16 +1065,18 @@ const char* File::GetCanonicalPath(Namespace* namespc, // Remove leading \\?\ since it is only to overcome MAX_PATH limitation. // Leave it if input used it though. int offset = 0; - if ((result_size > 4) && (wcsncmp(path.get(), L"\\\\?\\", 4) == 0) && + if ((result_size > 4) && + (wcsncmp(canonical_path.get(), L"\\\\?\\", 4) == 0) && (strncmp(pathname, "\\\\?\\", 4) != 0)) { - if ((result_size > 8) && (wcsncmp(path.get(), L"\\\\?\\UNC\\", 8) == 0)) { + if ((result_size > 8) && + (wcsncmp(canonical_path.get(), L"\\\\?\\UNC\\", 8) == 0)) { // Leave '\\?\UNC\' prefix intact - stripping it makes invalid UNC name. } else { offset = 4; } } - int utf8_size = WideCharToMultiByte(CP_UTF8, 0, path.get() + offset, -1, - nullptr, 0, nullptr, nullptr); + int utf8_size = WideCharToMultiByte(CP_UTF8, 0, canonical_path.get() + offset, + -1, nullptr, 0, nullptr, nullptr); if (dest == nullptr) { dest = DartUtils::ScopedCString(utf8_size); dest_size = utf8_size; @@ -1134,8 +1084,8 @@ const char* File::GetCanonicalPath(Namespace* namespc, if (dest_size != 0) { ASSERT(utf8_size <= dest_size); } - if (0 == WideCharToMultiByte(CP_UTF8, 0, path.get() + offset, -1, dest, - dest_size, nullptr, nullptr)) { + if (0 == WideCharToMultiByte(CP_UTF8, 0, canonical_path.get() + offset, -1, + dest, dest_size, nullptr, nullptr)) { return nullptr; } return dest; @@ -1157,27 +1107,17 @@ File::StdioHandleType File::GetStdioHandleType(int fd) { return kPipe; } -File::Type File::GetType(Namespace* namespc, - const char* pathname, - bool follow_links) { - // File::GetType can be called without scope(when launching isolate), - // so it mallocs prefixed path. - StringRAII string_raii = PrefixLongFilePathNoScope(pathname); - const char* prefixed_path = string_raii.str(); - - // Convert to wchar_t string. - Utf8ToWideScope name(prefixed_path); - DWORD attributes = GetFileAttributesW(name.wide()); +File::Type File::GetType(const wchar_t* path, bool follow_links) { + DWORD attributes = GetFileAttributesW(path); if (attributes == INVALID_FILE_ATTRIBUTES) { - return kDoesNotExist; + return File::kDoesNotExist; } else if ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { if (follow_links) { HANDLE target_handle = CreateFileW( - name.wide(), 0, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, - OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (target_handle == INVALID_HANDLE_VALUE) { - return kDoesNotExist; + return File::kDoesNotExist; } else { BY_HANDLE_FILE_INFORMATION info; if (!GetFileInformationByHandle(target_handle, &info)) { @@ -1186,16 +1126,23 @@ File::Type File::GetType(Namespace* namespc, } CloseHandle(target_handle); return ((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) - ? kIsDirectory - : kIsFile; + ? File::kIsDirectory + : File::kIsFile; } } else { - return kIsLink; + return File::kIsLink; } } else if ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { - return kIsDirectory; + return File::kIsDirectory; } - return kIsFile; + return File::kIsFile; +} + +File::Type File::GetType(Namespace* namespc, + const char* name, + bool follow_links) { + const auto path = ToWinAPIFilePath(name); + return GetType(path.get(), follow_links); } File::Identical File::AreIdentical(Namespace* namespc_1, @@ -1205,12 +1152,11 @@ File::Identical File::AreIdentical(Namespace* namespc_1, USE(namespc_1); USE(namespc_2); BY_HANDLE_FILE_INFORMATION file_info[2]; - const char* file_names[2] = {PrefixLongFilePath(file_1), - PrefixLongFilePath(file_2)}; + const std::unique_ptr file_names[2] = {ToWinAPIFilePath(file_1), + ToWinAPIFilePath(file_2)}; for (int i = 0; i < 2; ++i) { - Utf8ToWideScope wide_name(file_names[i]); HANDLE file_handle = CreateFileW( - wide_name.wide(), 0, + file_names[i].get(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); diff --git a/runtime/bin/file_win.h b/runtime/bin/file_win.h index ef5f73255ce..9ba7da02b71 100644 --- a/runtime/bin/file_win.h +++ b/runtime/bin/file_win.h @@ -5,6 +5,8 @@ #ifndef RUNTIME_BIN_FILE_WIN_H_ #define RUNTIME_BIN_FILE_WIN_H_ +#include + #include "bin/file.h" // The limit for a regular directory is 248. @@ -14,7 +16,18 @@ namespace dart { namespace bin { -const char* PrefixLongDirectoryPath(const char* path); +// Converts the given UTF8 path to wide char. If resulting path does not +// fit into MAX_DIRECTORY_PATH (or if |force_long_prefix| is true) then +// converts the path to the absolute `\\?\`-prefixed form. +// +// Note: +// 1. Some WinAPI functions (like SetCurrentDirectoryW) are always limited +// to MAX_PATH long paths and converting to `\\?\`-prefixed form does not +// remove this limitation. Always check Win API documentation. +// 2. This function might change relative path to an absolute path. +std::unique_ptr ToWinAPIDirectoryPath( + const char* path, + bool force_long_prefix = false); } // namespace bin } // namespace dart diff --git a/runtime/bin/namespace_win.cc b/runtime/bin/namespace_win.cc index 141ca38c9ee..297c44ca4fc 100644 --- a/runtime/bin/namespace_win.cc +++ b/runtime/bin/namespace_win.cc @@ -48,11 +48,14 @@ const char* Namespace::GetCurrent(Namespace* namespc) { } bool Namespace::SetCurrent(Namespace* namespc, const char* path) { - // TODO(zichangguo): "\\?\" prepended long path doesn't work. - // https://github.com/dart-lang/sdk/issues/42416 - path = PrefixLongDirectoryPath(path); - Utf8ToWideScope system_path(path); - bool result = SetCurrentDirectoryW(system_path.wide()) != 0; + // SetCurrentDirectory does not actually support paths larger than MAX_PATH, + // this limitation is due to the size of the internal buffer used for storing + // current directory. In Windows 10, version 1607, changes have been made + // to the OS to lift MAX_PATH limitations from file and directory management + // APIs, but both application and OS need to opt-in into new behavior. + // See https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry#enable-long-paths-in-windows-10-version-1607-and-later + const auto system_path = Utf8ToWideChar(path); + bool result = SetCurrentDirectoryW(system_path.get()) != 0; return result; } diff --git a/runtime/bin/socket_base_win.cc b/runtime/bin/socket_base_win.cc index 852395395e3..a46a2c024e3 100644 --- a/runtime/bin/socket_base_win.cc +++ b/runtime/bin/socket_base_win.cc @@ -307,12 +307,12 @@ bool SocketBase::ReverseLookup(const RawAddr& addr, bool SocketBase::ParseAddress(int type, const char* address, RawAddr* addr) { int result; - Utf8ToWideScope system_address(address); + const auto system_address = Utf8ToWideChar(address); if (type == SocketAddress::TYPE_IPV4) { - result = InetPton(AF_INET, system_address.wide(), &addr->in.sin_addr); + result = InetPton(AF_INET, system_address.get(), &addr->in.sin_addr); } else { ASSERT(type == SocketAddress::TYPE_IPV6); - result = InetPton(AF_INET6, system_address.wide(), &addr->in6.sin6_addr); + result = InetPton(AF_INET6, system_address.get(), &addr->in6.sin6_addr); } return result == 1; } diff --git a/runtime/bin/utils_win.cc b/runtime/bin/utils_win.cc index b05e4f28766..79a7eb80129 100644 --- a/runtime/bin/utils_win.cc +++ b/runtime/bin/utils_win.cc @@ -7,6 +7,7 @@ #include // NOLINT #include // NOLINT +#include #include "bin/utils.h" #include "bin/utils_win.h" @@ -237,6 +238,13 @@ void TimerUtils::Sleep(int64_t millis) { ::Sleep(millis); } +std::unique_ptr Utf8ToWideChar(const char* path) { + int wide_len = MultiByteToWideChar(CP_UTF8, 0, path, -1, nullptr, 0); + auto result = std::make_unique(wide_len); + MultiByteToWideChar(CP_UTF8, 0, path, -1, result.get(), wide_len); + return result; +} + } // namespace bin } // namespace dart diff --git a/runtime/bin/utils_win.h b/runtime/bin/utils_win.h index 61a761c890f..21fc212b353 100644 --- a/runtime/bin/utils_win.h +++ b/runtime/bin/utils_win.h @@ -5,6 +5,7 @@ #ifndef RUNTIME_BIN_UTILS_WIN_H_ #define RUNTIME_BIN_UTILS_WIN_H_ +#include #include #include "platform/utils.h" @@ -75,30 +76,7 @@ class WideToUtf8Scope { DISALLOW_IMPLICIT_CONSTRUCTORS(WideToUtf8Scope); }; -class Utf8ToWideScope { - public: - explicit Utf8ToWideScope(const char* utf8, intptr_t length = -1) { - int wide_len = MultiByteToWideChar(CP_UTF8, 0, utf8, length, nullptr, 0); - wchar_t* wide = - reinterpret_cast(malloc(sizeof(wchar_t) * wide_len)); - MultiByteToWideChar(CP_UTF8, 0, utf8, length, wide, wide_len); - length_ = wide_len; - wide_ = wide; - } - - ~Utf8ToWideScope() { free(wide_); } - - wchar_t* wide() const { return wide_; } - intptr_t length() const { return length_; } - intptr_t size_in_bytes() const { return length_ * sizeof(*wide_); } - - private: - intptr_t length_; - wchar_t* wide_; - - DISALLOW_ALLOCATION(); - DISALLOW_IMPLICIT_CONSTRUCTORS(Utf8ToWideScope); -}; +std::unique_ptr Utf8ToWideChar(const char* path); } // namespace bin } // namespace dart diff --git a/runtime/platform/globals.h b/runtime/platform/globals.h index c9f6dec27ef..29bbb969d1d 100644 --- a/runtime/platform/globals.h +++ b/runtime/platform/globals.h @@ -65,6 +65,7 @@ #endif #include +#define RPC_USE_NATIVE_WCHAR #include #include #include diff --git a/tests/standalone/io/directory_test.dart b/tests/standalone/io/directory_test.dart index 1053368d0ab..e58218943cf 100644 --- a/tests/standalone/io/directory_test.dart +++ b/tests/standalone/io/directory_test.dart @@ -251,6 +251,38 @@ class DirectoryTest { }); } + static void testExistsCreateDeleteWithTrailingBackslashSync() { + // Check that adding trailing backslash does not break anything on + // Windows. + if (!Platform.isWindows) { + return; + } + Directory d = Directory.systemTemp.createTempSync('dart_directory_test'); + try { + Directory d2 = new Directory('${d.path}\\'); + Expect.isTrue(d.existsSync()); + Expect.isTrue(d2.existsSync()); + Directory created1 = new Directory("${d.path}\\subdir\\"); + Directory created2 = new Directory("${d.path}\\subdir\\subdir\\"); + created1.createSync(); + created2.createSync(); + Expect.isTrue(created1.existsSync()); + Expect.isTrue(created2.existsSync()); + created2.deleteSync(); + created1.deleteSync(); + Expect.isFalse(created1.existsSync()); + Expect.isFalse(created2.existsSync()); + created1.createSync(); + created2.createSync(); + created1.deleteSync(recursive: true); + Expect.isFalse(created1.existsSync()); + Expect.isFalse(created2.existsSync()); + } finally { + d.deleteSync(recursive: true); + Expect.isFalse(d.existsSync()); + } + } + static void testExistsCreateDeleteSync() { Directory d = Directory.systemTemp.createTempSync('dart_directory_test'); Directory d2 = new Directory('${d.path}/'); @@ -437,6 +469,7 @@ class DirectoryTest { testDeleteTooLongNameSync(); testExistsCreateDelete(); testExistsCreateDeleteSync(); + testExistsCreateDeleteWithTrailingBackslashSync(); testDeleteLinkSync(); testDeleteLinkAsFileSync(); testDeleteBrokenLinkAsFileSync(); diff --git a/tests/standalone/io/file_long_path_test.dart b/tests/standalone/io/file_long_path_test.dart index 2ba7c8e0178..bf82bf310d8 100644 --- a/tests/standalone/io/file_long_path_test.dart +++ b/tests/standalone/io/file_long_path_test.dart @@ -6,30 +6,28 @@ import 'dart:io'; -import "package:expect/expect.dart"; +import 'package:expect/expect.dart'; +import 'package:path/path.dart' as p; const maxPath = 260; const maxDirectoryPath = maxPath - 12; String longName = '${'x' * 248}'; -Directory createLongPathDir(Directory tmp) { - if (tmp.path.length <= maxDirectoryPath) { - var path = tmp.path; - path += '\\${'t' * 248}'; - var dir = Directory(path); - dir.createSync(recursive: true); - // Test the rename() of directory - dir = dir.renameSync(tmp.path + '\\$longName'); - Expect.isTrue(dir.existsSync()); - Expect.isFalse(Directory(path).existsSync()); - return dir; - } else { - return tmp; - } +Directory createLongPathDir(Directory tmp, [String? suffix]) { + var path = tmp.path; + path = p.join(path, 't' * 248); + var dir = Directory(path); + dir.createSync(recursive: true); + Expect.isTrue(dir.existsSync()); + // Test the rename() of directory + dir = dir.renameSync(p.join(tmp.path, '$longName$suffix')); + Expect.isTrue(dir.existsSync()); + Expect.isFalse(Directory(path).existsSync()); + return dir; } void testCreate(String dir) { - final path = '${dir}\\a_long_path_filename'; + final path = p.join(dir, 'a_long_path_filename'); Expect.isTrue(path.length > maxPath); final file = File(path); file.createSync(); @@ -38,8 +36,8 @@ void testCreate(String dir) { } void testCopy(String dir) { - final src = '${dir}\\a_long_path_filename_1'; - final dest = '${dir}\\a_long_path_filename_2'; + final src = p.join(dir, 'a_long_path_filename_1'); + final dest = p.join(dir, 'a_long_path_filename_2'); Expect.isTrue(src.length > maxPath); final file1 = File(src); file1.createSync(); @@ -51,7 +49,7 @@ void testCopy(String dir) { } void testRename(String dir) { - final path = '${dir}\\a_long_path_filename'; + final path = p.join(dir, 'a_long_path_filename'); Expect.isTrue(path.length > maxPath); final file = File(path); file.createSync(); @@ -65,7 +63,7 @@ void testRename(String dir) { } void testReadWrite(String dir) { - final path = '${dir}\\a_long_path_filename'; + final path = p.join(dir, 'a_long_path_filename'); Expect.isTrue(path.length > maxPath); final file = File(path); @@ -82,7 +80,7 @@ void testReadWrite(String dir) { } void testOpen(String dir) { - final path = '${dir}\\a_long_path_filename'; + final path = p.join(dir, 'a_long_path_filename'); Expect.isTrue(path.length > maxPath); final file = File(path); file.createSync(); @@ -91,7 +89,7 @@ void testOpen(String dir) { } void testFileStat(String dir) { - final path = '${dir}\\a_long_path_filename'; + final path = p.join(dir, 'a_long_path_filename'); Expect.isTrue(path.length > maxPath); final file = File(path); file.createSync(); @@ -108,21 +106,37 @@ void testFileStat(String dir) { stat.accessed.toString(), file.lastAccessedSync().toString()); } -void testCreateLinkToDir(String dir) { - final linkPath = '$dir\\a_long_path_linkname'; - final renamedPath = '$dir\\a_long_renamed_path_linkname'; +String _createDirectoryHelper(String currentDir, String targetDir) { + if (p.isRelative(targetDir)) { + targetDir = p.normalize(p.absolute(currentDir, targetDir)); + } + final dir = Directory(targetDir)..createSync(); + Expect.isTrue(dir.existsSync()); + return dir.path; +} + +void testCreateLinkToDir(String dir, String dir2) { + final linkPath = p.join(dir, 'a_long_path_linkname'); + final renamedPath = p.join(dir, 'a_long_renamed_path_linkname'); Expect.isTrue(linkPath.length > maxPath); Expect.isTrue(renamedPath.length > maxPath); - var targetDirectory1 = '$dir\\a_long_directory_target1'; - var targetDirectory2 = '$dir\\a_long_directory_target2'; + final targetDirectory1 = + _createDirectoryHelper(dir, p.join(dir2, 'a_long_directory_target1')); + final targetDirectory2 = + _createDirectoryHelper(dir, p.join(dir2, 'a_long_directory_target2')); - Directory(targetDirectory1).createSync(); - Directory(targetDirectory2).createSync(); + final linkTarget1 = p.isRelative(dir2) + ? p.relative(targetDirectory1, from: p.dirname(p.absolute(linkPath))) + : targetDirectory1; + + final linkTarget2 = p.isRelative(dir2) + ? p.relative(targetDirectory2, from: p.dirname(p.absolute(linkPath))) + : targetDirectory2; // Create link - final link = Link(linkPath)..createSync(targetDirectory1); + final link = Link(linkPath)..createSync(linkTarget1); Expect.isTrue(link.existsSync()); final resolvedCreatePath = link.resolveSymbolicLinksSync(); Expect.isTrue( @@ -139,7 +153,7 @@ void testCreateLinkToDir(String dir) { '${link.path} should resolve to $targetDirectory1 but resolved to $resolvedRenamePath'); // Update link target - renamedLink.updateSync(targetDirectory2); + renamedLink.updateSync(linkTarget2); final resolvedUpdatedPath = renamedLink.resolveSymbolicLinksSync(); Expect.isTrue( FileSystemEntity.identicalSync(targetDirectory2, resolvedUpdatedPath), @@ -150,11 +164,30 @@ void testCreateLinkToDir(String dir) { renamedLink.deleteSync(); } -void testCreateLinkToFile(String dir) { - final path = '${dir}\\a_long_path_linkname'; +void testCreateLinkToFile(String dir, String dir2) { + final path = p.join(dir, 'a_long_path_linkname'); Expect.isTrue(path.length > maxPath); - var target = '$dir\\a_long_path_target'; - final link = Link(path)..createSync(target); + + String _pathHelper(String currentDir, String targetPath) { + if (p.isRelative(targetPath)) { + return p.normalize(p.absolute(currentDir, targetPath)); + } else { + return targetPath; + } + } + + var target = _pathHelper(dir, p.join(dir2, 'a_long_path_target')); + var target2 = _pathHelper(dir, p.join(dir2, 'an_updated_target')); + + var linkTarget = p.isRelative(dir2) + ? p.relative(target, from: p.dirname(p.absolute(path))) + : target; + + var linkTarget2 = p.isRelative(dir2) + ? p.relative(target2, from: p.dirname(p.absolute(path))) + : target2; + + final link = Link(path)..createSync(linkTarget); final dest = File(target)..createSync(); Expect.isTrue(dest.existsSync()); @@ -165,15 +198,14 @@ void testCreateLinkToFile(String dir) { '${link.path} should resolve to $target but resolved to $resolvedPath'); // Rename link - var renamedLink = link.renameSync('${dir}\\a_renamed_long_path_link'); + var renamedLink = link.renameSync(p.join(dir, 'a_renamed_long_path_link')); Expect.isTrue(renamedLink.existsSync()); Expect.isFalse(link.existsSync()); Expect.isTrue(renamedLink.targetSync().contains('a_long_path_target')); // Update link target - target = '$dir\\an_updated_target'; - final renamedDest = File(target)..createSync(); - renamedLink.updateSync(target); + final renamedDest = File(target2)..createSync(); + renamedLink.updateSync(linkTarget2); Expect.isTrue(renamedLink.targetSync().contains('an_updated_target')); dest.deleteSync(); @@ -182,8 +214,8 @@ void testCreateLinkToFile(String dir) { } testNormalLinkToLongPath(String short, String long) { - var target = File('$long\\file_target')..createSync(); - final link = Link('$short\\link')..createSync(target.path); + var target = File(p.join(long, 'file_target'))..createSync(); + final link = Link(p.join(short, 'link'))..createSync(target.path); Expect.isTrue(target.path.length > maxPath); Expect.isTrue(link.resolveSymbolicLinksSync().length > maxPath); Expect.isTrue(link.path.length < maxPath); @@ -192,7 +224,7 @@ testNormalLinkToLongPath(String short, String long) { Expect.isTrue(link.existsSync()); Expect.equals(target.path, link.targetSync()); - var targetDir = Directory('$long\\dir_target')..createSync(); + var targetDir = Directory(p.join(long, 'dir_target'))..createSync(); link.updateSync(targetDir.path); Expect.equals(targetDir.path, link.targetSync()); @@ -202,8 +234,8 @@ testNormalLinkToLongPath(String short, String long) { } testLongPathLinkToNormal(String short, String long) { - var target = File('$short\\file_target')..createSync(); - final link = Link('$long\\link')..createSync(target.path); + var target = File(p.join(short, 'file_target'))..createSync(); + final link = Link(p.join(long, 'link'))..createSync(target.path); Expect.isTrue(target.path.length < maxPath); Expect.isTrue(link.path.length > maxPath); @@ -212,7 +244,7 @@ testLongPathLinkToNormal(String short, String long) { Expect.isTrue(link.existsSync()); Expect.equals(target.path, link.targetSync()); - var targetDir = Directory('$short\\dir_target')..createSync(); + var targetDir = Directory(p.join(short, 'dir_target'))..createSync(); link.updateSync(targetDir.path); Expect.equals(targetDir.path, link.targetSync()); @@ -221,38 +253,49 @@ testLongPathLinkToNormal(String short, String long) { targetDir.deleteSync(); } -testDirectorySetCurrent(String dir) { +testDirectorySetCurrentFails(String dir) { // This tests setting a long path directory to current directory. - // This will fail. Expect.isTrue(dir.length > maxPath); - Expect.throws(() { - Directory.current = dir; - }, (e) => e.toString().contains('extension is too long')); + if (Platform.isWindows) { + // On Windows current directory path is limited to MAX_PATH characters. + // Windows 10, Version 1607 introduced a way to lift this limitation but + // it requires opt-in from both application and the OS configuration. + // + // See https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry#enable-long-paths-in-windows-10-version-1607-and-later + Expect.throws(() { + Directory.current = dir; + }, (e) => e.toString().contains('extension is too long')); + } } void main() { - if (!Platform.isWindows) { - return; - } - final tmp = Directory.systemTemp.createTempSync('dart-file-long-path'); + final tmp = Directory.systemTemp.createTempSync('flpt'); final oldCurrent = Directory.current; Directory.current = tmp; try { - String dir = createLongPathDir(tmp).path; - testDirectorySetCurrent(dir); - for (final path in [dir, ".\\$longName", ".\\$longName\\..\\$longName"]) { + String dir1 = createLongPathDir(tmp, 'dir1').path; + String dir2 = createLongPathDir(tmp, 'dir2').path; + testDirectorySetCurrentFails(dir1); + for (final path in [ + dir1, + p.join('.', '${longName}dir1'), + p.join('.', '${longName}dir1', '..', '${longName}dir1'), + ]) { testCreate(path); testCopy(path); testRename(path); testReadWrite(path); testOpen(path); testFileStat(path); - testCreateLinkToDir(path); - testCreateLinkToFile(path); + for (var relative in [true, false]) { + final targetDir = relative ? p.relative(dir2, from: dir1) : dir2; + testCreateLinkToDir(path, targetDir); + testCreateLinkToFile(path, targetDir); + } } - testNormalLinkToLongPath(tmp.path, dir); - testLongPathLinkToNormal(tmp.path, dir); + testNormalLinkToLongPath(tmp.path, dir1); + testLongPathLinkToNormal(tmp.path, dir1); } finally { // Reset the current Directory. Directory.current = oldCurrent;