mirror of
https://github.com/dart-lang/sdk
synced 2024-10-02 23:49:17 +00:00
Reland "Enable long path on Windows"
This reverts commit f8fbefd951
in patchset 1 with fixes to the way how temporary strings are allocated and the way how prefixed string is returned in successive patchsets.
Fixes https://github.com/dart-lang/sdk/issues/42416
Change-Id: Idb801cb117fc2d84fad4c533f5239d8499afc943
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/164741
Commit-Queue: Alexander Aprelev <aam@google.com>
Reviewed-by: Siva Annamalai <asiva@google.com>
This commit is contained in:
parent
a6dafabb88
commit
48e5cba6b9
|
@ -167,9 +167,14 @@ applications (issue [flutter/flutter#63038][]).
|
|||
* [Abstract Unix Domain Socket][] is supported on Linux/Android now. Using an
|
||||
`InternetAddress` with `address` starting with '@' and type being
|
||||
`InternetAddressType.Unix` will create an abstract Unix Domain Socket.
|
||||
* On Windows, file APIs can now handle files and directories identified by
|
||||
long paths (greater than 260 characters). It complies with all restrictions
|
||||
from [Long Path on Windows][]. Note that `Directory.current` does not work
|
||||
with long path.
|
||||
|
||||
[#42006]: https://github.com/dart-lang/sdk/issues/42006
|
||||
[Abstract Unix Domain Socket]: http://man7.org/linux/man-pages/man7/unix.7.html
|
||||
[Long Path on Windows]: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation
|
||||
|
||||
#### `dart:html`
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ builtin_impl_sources = [
|
|||
"file_macos.cc",
|
||||
"file_support.cc",
|
||||
"file_win.cc",
|
||||
"file_win.h",
|
||||
"io_buffer.cc",
|
||||
"io_buffer.h",
|
||||
"isolate_data.cc",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "bin/crypto.h"
|
||||
#include "bin/dartutils.h"
|
||||
#include "bin/file.h"
|
||||
#include "bin/file_win.h"
|
||||
#include "bin/namespace.h"
|
||||
#include "bin/utils.h"
|
||||
#include "bin/utils_win.h"
|
||||
|
@ -21,8 +22,6 @@
|
|||
|
||||
#undef DeleteFile
|
||||
|
||||
#define MAX_LONG_PATH 32767
|
||||
|
||||
namespace dart {
|
||||
namespace bin {
|
||||
|
||||
|
@ -278,7 +277,12 @@ static bool DeleteEntry(LPWIN32_FIND_DATAW find_file_data, PathBuffer* path) {
|
|||
}
|
||||
|
||||
static bool DeleteRecursively(PathBuffer* path) {
|
||||
DWORD attributes = GetFileAttributesW(path->AsStringW());
|
||||
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;
|
||||
}
|
||||
|
@ -286,33 +290,34 @@ static bool DeleteRecursively(PathBuffer* path) {
|
|||
// filesystem that we do not want to recurse into.
|
||||
if ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
|
||||
// Just delete the junction itself.
|
||||
return RemoveDirectoryW(path->AsStringW()) != 0;
|
||||
return RemoveDirectoryW(prefixed_path.AsStringW()) != 0;
|
||||
}
|
||||
// If it's a file, remove it directly.
|
||||
if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
|
||||
return DeleteFile(L"", path);
|
||||
return DeleteFile(L"", &prefixed_path);
|
||||
}
|
||||
|
||||
if (!path->AddW(L"\\*")) {
|
||||
if (!prefixed_path.AddW(L"\\*")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WIN32_FIND_DATAW find_file_data;
|
||||
HANDLE find_handle = FindFirstFileW(path->AsStringW(), &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 = path->length() - 1;
|
||||
path->Reset(path_length);
|
||||
int path_length = prefixed_path.length() - 1;
|
||||
prefixed_path.Reset(path_length);
|
||||
|
||||
do {
|
||||
if (!DeleteEntry(&find_file_data, path)) {
|
||||
if (!DeleteEntry(&find_file_data, &prefixed_path)) {
|
||||
break;
|
||||
}
|
||||
path->Reset(path_length); // DeleteEntry adds to the path.
|
||||
prefixed_path.Reset(path_length); // DeleteEntry adds to the path.
|
||||
} while (FindNextFileW(find_handle, &find_file_data) != 0);
|
||||
|
||||
DWORD last_error = GetLastError();
|
||||
|
@ -324,8 +329,9 @@ static bool DeleteRecursively(PathBuffer* path) {
|
|||
return false;
|
||||
}
|
||||
// All content deleted succesfully, try to delete directory.
|
||||
path->Reset(path_length - 1); // Drop the "\" from the end of the path.
|
||||
return RemoveDirectoryW(path->AsStringW()) != 0;
|
||||
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) {
|
||||
|
@ -349,7 +355,8 @@ static Directory::ExistsResult ExistsHelper(const wchar_t* dir_name) {
|
|||
|
||||
Directory::ExistsResult Directory::Exists(Namespace* namespc,
|
||||
const char* dir_name) {
|
||||
Utf8ToWideScope system_name(dir_name);
|
||||
const char* prefixed_dir_name = PrefixLongDirectoryPath(dir_name);
|
||||
Utf8ToWideScope system_name(prefixed_dir_name);
|
||||
return ExistsHelper(system_name.wide());
|
||||
}
|
||||
|
||||
|
@ -369,7 +376,8 @@ char* Directory::CurrentNoScope() {
|
|||
}
|
||||
|
||||
bool Directory::Create(Namespace* namespc, const char* dir_name) {
|
||||
Utf8ToWideScope system_name(dir_name);
|
||||
const char* prefixed_dir_name = PrefixLongDirectoryPath(dir_name);
|
||||
Utf8ToWideScope system_name(prefixed_dir_name);
|
||||
int create_status = CreateDirectoryW(system_name.wide(), NULL);
|
||||
// If the directory already existed, treat it as a success.
|
||||
if ((create_status == 0) && (GetLastError() == ERROR_ALREADY_EXISTS) &&
|
||||
|
@ -475,10 +483,11 @@ 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);
|
||||
bool result = false;
|
||||
Utf8ToWideScope system_dir_name(dir_name);
|
||||
Utf8ToWideScope system_dir_name(prefixed_dir_name);
|
||||
if (!recursive) {
|
||||
if (File::GetType(namespc, dir_name, true) == File::kIsDirectory) {
|
||||
if (File::GetType(namespc, prefixed_dir_name, true) == File::kIsDirectory) {
|
||||
result = (RemoveDirectoryW(system_dir_name.wide()) != 0);
|
||||
} else {
|
||||
SetLastError(ERROR_FILE_NOT_FOUND);
|
||||
|
@ -495,18 +504,20 @@ bool Directory::Delete(Namespace* namespc,
|
|||
bool Directory::Rename(Namespace* namespc,
|
||||
const char* path,
|
||||
const char* new_path) {
|
||||
Utf8ToWideScope system_path(path);
|
||||
Utf8ToWideScope system_new_path(new_path);
|
||||
const char* prefixed_dir = PrefixLongDirectoryPath(path);
|
||||
Utf8ToWideScope system_path(prefixed_dir);
|
||||
ExistsResult exists = ExistsHelper(system_path.wide());
|
||||
if (exists != EXISTS) {
|
||||
return false;
|
||||
}
|
||||
const char* prefixed_new_dir = PrefixLongDirectoryPath(new_path);
|
||||
Utf8ToWideScope system_new_path(prefixed_new_dir);
|
||||
ExistsResult new_exists = ExistsHelper(system_new_path.wide());
|
||||
// MoveFile does not allow replacing existing directories. Therefore,
|
||||
// if the new_path is currently a directory we need to delete it
|
||||
// first.
|
||||
if (new_exists == EXISTS) {
|
||||
bool success = Delete(namespc, new_path, true);
|
||||
bool success = Delete(namespc, prefixed_new_dir, true);
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -5,13 +5,15 @@
|
|||
#include "platform/globals.h"
|
||||
#if defined(HOST_OS_WINDOWS)
|
||||
|
||||
#include "bin/file.h"
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <Shlwapi.h> // NOLINT
|
||||
#include <WinIoCtl.h> // NOLINT
|
||||
#include <fcntl.h> // NOLINT
|
||||
#include <io.h> // NOLINT
|
||||
#include <Shlwapi.h> // NOLINT
|
||||
#undef StrDup // defined in Shlwapi.h as StrDupW
|
||||
#undef StrDup // defined in Shlwapi.h as StrDupW
|
||||
#include <stdio.h> // NOLINT
|
||||
#include <string.h> // NOLINT
|
||||
#include <sys/stat.h> // NOLINT
|
||||
|
@ -20,6 +22,8 @@
|
|||
#include "bin/builtin.h"
|
||||
#include "bin/crypto.h"
|
||||
#include "bin/directory.h"
|
||||
#include "bin/file.h"
|
||||
#include "bin/file_win.h"
|
||||
#include "bin/namespace.h"
|
||||
#include "bin/utils.h"
|
||||
#include "bin/utils_win.h"
|
||||
|
@ -298,8 +302,146 @@ File* File::FileOpenW(const wchar_t* system_name, FileOpenMode mode) {
|
|||
return new File(new FileHandle(fd));
|
||||
}
|
||||
|
||||
class StringRAII {
|
||||
public:
|
||||
explicit StringRAII(const char* s) : s_(s), own_(false) {}
|
||||
explicit StringRAII(char* s) : s_(s), own_(true) {}
|
||||
~StringRAII() {
|
||||
if (own_) {
|
||||
free(const_cast<char*>(s_));
|
||||
}
|
||||
}
|
||||
const char* str() const { return s_; }
|
||||
const char* release() {
|
||||
own_ = false;
|
||||
return s_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool own_;
|
||||
const char* s_;
|
||||
};
|
||||
|
||||
class Wchart {
|
||||
public:
|
||||
explicit Wchart(int size) {
|
||||
buf_ = reinterpret_cast<wchar_t*>(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(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(strdup(scope.utf8()));
|
||||
}
|
||||
|
||||
static StringRAII PrefixLongPathIfExceedLimit(
|
||||
const char* path,
|
||||
bool is_file,
|
||||
std::function<char*(int)> allocate) {
|
||||
// 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 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);
|
||||
}
|
||||
|
||||
// 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)
|
||||
: ConvertToAbsolutePath(path, &is_ok);
|
||||
if (!is_ok) {
|
||||
return StringRAII(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);
|
||||
}
|
||||
|
||||
// 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];
|
||||
}
|
||||
result[length + kLongPathPrefixLength] = '\0';
|
||||
return StringRAII(result);
|
||||
}
|
||||
|
||||
static const char* PrefixLongFilePath(const char* path) {
|
||||
return PrefixLongPathIfExceedLimit(
|
||||
path, /*is_file=*/true,
|
||||
[](int size) {
|
||||
return reinterpret_cast<char*>(Dart_ScopeAllocate(size));
|
||||
})
|
||||
.release();
|
||||
}
|
||||
|
||||
static StringRAII PrefixLongFilePathNoScope(const char* path) {
|
||||
return PrefixLongPathIfExceedLimit(path, /*is_file=*/true, [](int size) {
|
||||
return reinterpret_cast<char*>(malloc(size));
|
||||
});
|
||||
}
|
||||
|
||||
const char* PrefixLongDirectoryPath(const char* path) {
|
||||
return PrefixLongPathIfExceedLimit(
|
||||
path, /*is_file=*/false,
|
||||
[](int size) {
|
||||
return reinterpret_cast<char*>(Dart_ScopeAllocate(size));
|
||||
})
|
||||
.release();
|
||||
}
|
||||
|
||||
File* File::Open(Namespace* namespc, const char* path, FileOpenMode mode) {
|
||||
Utf8ToWideScope system_name(path);
|
||||
// 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);
|
||||
return file;
|
||||
}
|
||||
|
@ -364,8 +506,9 @@ static bool StatHelper(wchar_t* path, struct __stat64* st) {
|
|||
}
|
||||
|
||||
bool File::Exists(Namespace* namespc, const char* name) {
|
||||
StringRAII string_raii = PrefixLongFilePathNoScope(name);
|
||||
Utf8ToWideScope system_name(string_raii.str());
|
||||
struct __stat64 st;
|
||||
Utf8ToWideScope system_name(name);
|
||||
return StatHelper(system_name.wide(), &st);
|
||||
}
|
||||
|
||||
|
@ -379,7 +522,7 @@ bool File::ExistsUri(Namespace* namespc, const char* uri) {
|
|||
}
|
||||
|
||||
bool File::Create(Namespace* namespc, const char* name) {
|
||||
Utf8ToWideScope system_name(name);
|
||||
Utf8ToWideScope system_name(PrefixLongFilePath(name));
|
||||
int fd = _wopen(system_name.wide(), O_RDONLY | O_CREAT, 0666);
|
||||
if (fd < 0) {
|
||||
return false;
|
||||
|
@ -426,8 +569,8 @@ static const int kMountPointHeaderSize = 4 * sizeof(USHORT);
|
|||
bool File::CreateLink(Namespace* namespc,
|
||||
const char* utf8_name,
|
||||
const char* utf8_target) {
|
||||
Utf8ToWideScope name(utf8_name);
|
||||
Utf8ToWideScope target(utf8_target);
|
||||
Utf8ToWideScope name(PrefixLongFilePath(utf8_name));
|
||||
Utf8ToWideScope target(PrefixLongFilePath(utf8_target));
|
||||
DWORD flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
|
||||
|
||||
File::Type type = File::GetType(namespc, utf8_target, true);
|
||||
|
@ -449,13 +592,13 @@ bool File::CreateLink(Namespace* namespc,
|
|||
}
|
||||
|
||||
bool File::Delete(Namespace* namespc, const char* name) {
|
||||
Utf8ToWideScope system_name(name);
|
||||
Utf8ToWideScope system_name(PrefixLongFilePath(name));
|
||||
int status = _wremove(system_name.wide());
|
||||
return status != -1;
|
||||
}
|
||||
|
||||
bool File::DeleteLink(Namespace* namespc, const char* name) {
|
||||
Utf8ToWideScope system_name(name);
|
||||
Utf8ToWideScope system_name(PrefixLongFilePath(name));
|
||||
bool result = false;
|
||||
DWORD attributes = GetFileAttributesW(system_name.wide());
|
||||
if ((attributes == INVALID_FILE_ATTRIBUTES) ||
|
||||
|
@ -477,13 +620,15 @@ bool File::DeleteLink(Namespace* namespc, const char* name) {
|
|||
bool File::Rename(Namespace* namespc,
|
||||
const char* old_path,
|
||||
const char* new_path) {
|
||||
File::Type type = GetType(namespc, old_path, false);
|
||||
const char* prefixed_old_path = PrefixLongFilePath(old_path);
|
||||
File::Type type = GetType(namespc, prefixed_old_path, false);
|
||||
if (type != kIsFile) {
|
||||
SetLastError(ERROR_FILE_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
Utf8ToWideScope system_old_path(old_path);
|
||||
Utf8ToWideScope system_new_path(new_path);
|
||||
const char* prefixed_new_path = PrefixLongFilePath(new_path);
|
||||
Utf8ToWideScope system_old_path(prefixed_old_path);
|
||||
Utf8ToWideScope system_new_path(prefixed_new_path);
|
||||
DWORD flags = MOVEFILE_WRITE_THROUGH | MOVEFILE_REPLACE_EXISTING;
|
||||
int move_status =
|
||||
MoveFileExW(system_old_path.wide(), system_new_path.wide(), flags);
|
||||
|
@ -493,23 +638,25 @@ bool File::Rename(Namespace* namespc,
|
|||
bool File::RenameLink(Namespace* namespc,
|
||||
const char* old_path,
|
||||
const char* new_path) {
|
||||
File::Type type = GetType(namespc, old_path, false);
|
||||
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(old_path);
|
||||
Utf8ToWideScope system_new_path(new_path);
|
||||
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;
|
||||
|
||||
// Junction links 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 a link. This step is only
|
||||
// necessary for junctions created by the old Link.create implementation.
|
||||
if ((Directory::Exists(namespc, new_path) == Directory::EXISTS) &&
|
||||
(GetType(namespc, new_path, false) == kIsLink)) {
|
||||
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, new_path)) {
|
||||
if (!DeleteLink(namespc, prefixed_new_path)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -615,24 +762,26 @@ static wchar_t* CopyIntoTempFile(const char* src, const char* dest) {
|
|||
bool File::Copy(Namespace* namespc,
|
||||
const char* old_path,
|
||||
const char* new_path) {
|
||||
File::Type type = GetType(namespc, old_path, false);
|
||||
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);
|
||||
if (type != kIsFile) {
|
||||
SetLastError(ERROR_FILE_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
|
||||
wchar_t* temp_file = CopyIntoTempFile(old_path, new_path);
|
||||
wchar_t* temp_file = CopyIntoTempFile(prefixed_old_path, prefixed_new_path);
|
||||
if (temp_file == NULL) {
|
||||
// If temp file creation fails, fall back on doing a direct copy.
|
||||
Utf8ToWideScope system_old_path(old_path);
|
||||
Utf8ToWideScope system_new_path(new_path);
|
||||
Utf8ToWideScope system_old_path(prefixed_old_path);
|
||||
Utf8ToWideScope system_new_path(prefixed_new_path);
|
||||
return CopyFileExW(system_old_path.wide(), system_new_path.wide(), NULL,
|
||||
NULL, NULL, 0) != 0;
|
||||
}
|
||||
Utf8ToWideScope system_new_dest(new_path);
|
||||
Utf8ToWideScope system_new_dest(prefixed_new_path);
|
||||
|
||||
// Remove the existing file. Otherwise, renaming will fail.
|
||||
if (Exists(namespc, new_path)) {
|
||||
if (Exists(namespc, prefixed_new_path)) {
|
||||
DeleteFileW(system_new_dest.wide());
|
||||
}
|
||||
|
||||
|
@ -647,7 +796,7 @@ bool File::Copy(Namespace* namespc,
|
|||
|
||||
int64_t File::LengthFromPath(Namespace* namespc, const char* name) {
|
||||
struct __stat64 st;
|
||||
Utf8ToWideScope system_name(name);
|
||||
Utf8ToWideScope system_name(PrefixLongFilePath(name));
|
||||
if (!StatHelper(system_name.wide(), &st)) {
|
||||
return -1;
|
||||
}
|
||||
|
@ -658,7 +807,8 @@ const char* File::LinkTarget(Namespace* namespc,
|
|||
const char* pathname,
|
||||
char* dest,
|
||||
int dest_size) {
|
||||
const wchar_t* name = StringUtilsWin::Utf8ToWide(pathname);
|
||||
const wchar_t* name =
|
||||
StringUtilsWin::Utf8ToWide(PrefixLongFilePath(pathname));
|
||||
HANDLE dir_handle = CreateFileW(
|
||||
name, GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
|
||||
|
@ -668,8 +818,10 @@ const char* File::LinkTarget(Namespace* namespc,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
// Allocate a buffer for regular paths (smaller than MAX_PATH). If buffer is
|
||||
// too small for a long path, allocate a bigger buffer and try again.
|
||||
int buffer_size =
|
||||
sizeof(REPARSE_DATA_BUFFER) + 2 * (MAX_PATH + 1) * sizeof(WCHAR);
|
||||
sizeof(REPARSE_DATA_BUFFER) + (MAX_PATH + 1) * sizeof(WCHAR);
|
||||
REPARSE_DATA_BUFFER* buffer =
|
||||
reinterpret_cast<REPARSE_DATA_BUFFER*>(Dart_ScopeAllocate(buffer_size));
|
||||
DWORD received_bytes; // Value is not used.
|
||||
|
@ -677,9 +829,26 @@ const char* File::LinkTarget(Namespace* namespc,
|
|||
buffer, buffer_size, &received_bytes, NULL);
|
||||
if (result == 0) {
|
||||
DWORD error = GetLastError();
|
||||
CloseHandle(dir_handle);
|
||||
SetLastError(error);
|
||||
return NULL;
|
||||
// If ERROR_MORE_DATA is thrown, the target path exceeds the size limit. A
|
||||
// bigger buffer will be required.
|
||||
if (error == ERROR_MORE_DATA) {
|
||||
// Allocate a bigger buffer with MAX_LONG_PATH
|
||||
buffer_size =
|
||||
sizeof(REPARSE_DATA_BUFFER) + (MAX_LONG_PATH + 1) * sizeof(WCHAR);
|
||||
buffer = reinterpret_cast<REPARSE_DATA_BUFFER*>(
|
||||
Dart_ScopeAllocate(buffer_size));
|
||||
result = DeviceIoControl(dir_handle, FSCTL_GET_REPARSE_POINT, NULL, 0,
|
||||
buffer, buffer_size, &received_bytes, NULL);
|
||||
if (result == 0) {
|
||||
// Overwrite the ERROR_MORE_DATA.
|
||||
error = GetLastError();
|
||||
}
|
||||
}
|
||||
if (result == 0) {
|
||||
CloseHandle(dir_handle);
|
||||
SetLastError(error);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (CloseHandle(dir_handle) == 0) {
|
||||
return NULL;
|
||||
|
@ -726,11 +895,12 @@ const char* File::LinkTarget(Namespace* namespc,
|
|||
}
|
||||
|
||||
void File::Stat(Namespace* namespc, const char* name, int64_t* data) {
|
||||
File::Type type = GetType(namespc, name, false);
|
||||
const char* prefixed_name = PrefixLongFilePath(name);
|
||||
File::Type type = GetType(namespc, prefixed_name, false);
|
||||
data[kType] = type;
|
||||
if (type != kDoesNotExist) {
|
||||
struct _stat64 st;
|
||||
Utf8ToWideScope system_name(name);
|
||||
Utf8ToWideScope system_name(prefixed_name);
|
||||
int stat_status = _wstat64(system_name.wide(), &st);
|
||||
if (stat_status == 0) {
|
||||
data[kCreatedTime] = st.st_ctime * 1000;
|
||||
|
@ -746,7 +916,7 @@ 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(name);
|
||||
Utf8ToWideScope system_name(PrefixLongFilePath(name));
|
||||
if (!StatHelper(system_name.wide(), &st)) {
|
||||
return -1;
|
||||
}
|
||||
|
@ -755,7 +925,7 @@ time_t File::LastAccessed(Namespace* namespc, const char* name) {
|
|||
|
||||
time_t File::LastModified(Namespace* namespc, const char* name) {
|
||||
struct __stat64 st;
|
||||
Utf8ToWideScope system_name(name);
|
||||
Utf8ToWideScope system_name(PrefixLongFilePath(name));
|
||||
if (!StatHelper(system_name.wide(), &st)) {
|
||||
return -1;
|
||||
}
|
||||
|
@ -767,7 +937,7 @@ bool File::SetLastAccessed(Namespace* namespc,
|
|||
int64_t millis) {
|
||||
// First get the current times.
|
||||
struct __stat64 st;
|
||||
Utf8ToWideScope system_name(name);
|
||||
Utf8ToWideScope system_name(PrefixLongFilePath(name));
|
||||
if (!StatHelper(system_name.wide(), &st)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -784,7 +954,7 @@ bool File::SetLastModified(Namespace* namespc,
|
|||
int64_t millis) {
|
||||
// First get the current times.
|
||||
struct __stat64 st;
|
||||
Utf8ToWideScope system_name(name);
|
||||
Utf8ToWideScope system_name(PrefixLongFilePath(name));
|
||||
if (!StatHelper(system_name.wide(), &st)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -801,7 +971,6 @@ bool File::SetLastModified(Namespace* namespc,
|
|||
bool File::IsAbsolutePath(const char* pathname) {
|
||||
if (pathname == NULL) return false;
|
||||
char first = pathname[0];
|
||||
if (pathname == 0) return false;
|
||||
char second = pathname[1];
|
||||
if (first == '\\' && second == '\\') return true;
|
||||
if (second != ':') return false;
|
||||
|
@ -814,7 +983,7 @@ const char* File::GetCanonicalPath(Namespace* namespc,
|
|||
const char* pathname,
|
||||
char* dest,
|
||||
int dest_size) {
|
||||
Utf8ToWideScope system_name(pathname);
|
||||
Utf8ToWideScope system_name(PrefixLongFilePath(pathname));
|
||||
HANDLE file_handle =
|
||||
CreateFileW(system_name.wide(), 0, FILE_SHARE_READ, NULL, OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
||||
|
@ -838,9 +1007,8 @@ const char* File::GetCanonicalPath(Namespace* namespc,
|
|||
|
||||
// Remove leading \\?\ if possible, unless input used it.
|
||||
int offset = 0;
|
||||
if ((result_size < MAX_PATH - 1 + 4) && (result_size > 4) &&
|
||||
(wcsncmp(path.get(), L"\\\\?\\", 4) == 0) &&
|
||||
(wcsncmp(system_name.wide(), L"\\\\?\\", 4) != 0)) {
|
||||
if ((result_size > 4) && (wcsncmp(path.get(), L"\\\\?\\", 4) == 0) &&
|
||||
(strncmp(pathname, "\\\\?\\", 4) != 0)) {
|
||||
offset = 4;
|
||||
}
|
||||
int utf8_size = WideCharToMultiByte(CP_UTF8, 0, path.get() + offset, -1,
|
||||
|
@ -878,8 +1046,13 @@ File::StdioHandleType File::GetStdioHandleType(int fd) {
|
|||
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(pathname);
|
||||
Utf8ToWideScope name(prefixed_path);
|
||||
DWORD attributes = GetFileAttributesW(name.wide());
|
||||
File::Type result = kIsFile;
|
||||
if (attributes == INVALID_FILE_ATTRIBUTES) {
|
||||
|
@ -919,7 +1092,8 @@ 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] = {file_1, file_2};
|
||||
const char* file_names[2] = {PrefixLongFilePath(file_1),
|
||||
PrefixLongFilePath(file_2)};
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
Utf8ToWideScope wide_name(file_names[i]);
|
||||
HANDLE file_handle = CreateFileW(
|
||||
|
|
22
runtime/bin/file_win.h
Normal file
22
runtime/bin/file_win.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef RUNTIME_BIN_FILE_WIN_H_
|
||||
#define RUNTIME_BIN_FILE_WIN_H_
|
||||
|
||||
#include "bin/file.h"
|
||||
|
||||
// The limit for a regular directory is 248.
|
||||
// Reference: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation
|
||||
#define MAX_DIRECTORY_PATH (MAX_PATH - 12)
|
||||
|
||||
namespace dart {
|
||||
namespace bin {
|
||||
|
||||
const char* PrefixLongDirectoryPath(const char* path);
|
||||
|
||||
} // namespace bin
|
||||
} // namespace dart
|
||||
|
||||
#endif // RUNTIME_BIN_FILE_WIN_H_
|
|
@ -10,6 +10,8 @@
|
|||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "bin/file.h"
|
||||
#include "bin/file_win.h"
|
||||
#include "bin/utils.h"
|
||||
#include "bin/utils_win.h"
|
||||
|
||||
|
@ -46,6 +48,9 @@ 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;
|
||||
return result;
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
#include "platform/utils.h"
|
||||
|
||||
#define MAX_LONG_PATH 32767
|
||||
|
||||
namespace dart {
|
||||
namespace bin {
|
||||
|
||||
|
|
247
tests/standalone/io/file_long_path_test.dart
Normal file
247
tests/standalone/io/file_long_path_test.dart
Normal file
|
@ -0,0 +1,247 @@
|
|||
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
//
|
||||
// This test is Windows-only.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import "package:expect/expect.dart";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void testCreate(String dir) {
|
||||
final path = '${dir}\\a_long_path_filename';
|
||||
Expect.isTrue(path.length > maxPath);
|
||||
final file = File(path);
|
||||
file.createSync();
|
||||
Expect.isTrue(file.existsSync());
|
||||
file.deleteSync();
|
||||
}
|
||||
|
||||
void testCopy(String dir) {
|
||||
final src = '${dir}\\a_long_path_filename_1';
|
||||
final dest = '${dir}\\a_long_path_filename_2';
|
||||
Expect.isTrue(src.length > maxPath);
|
||||
final file1 = File(src);
|
||||
file1.createSync();
|
||||
|
||||
final file2 = file1.copySync(dest);
|
||||
Expect.isTrue(file2.existsSync());
|
||||
file1.deleteSync();
|
||||
file2.deleteSync();
|
||||
}
|
||||
|
||||
void testRename(String dir) {
|
||||
final path = '${dir}\\a_long_path_filename';
|
||||
Expect.isTrue(path.length > maxPath);
|
||||
final file = File(path);
|
||||
file.createSync();
|
||||
Expect.isTrue(file.existsSync());
|
||||
|
||||
final renamedFile = file.renameSync('${path}_copy');
|
||||
|
||||
Expect.isFalse(file.existsSync());
|
||||
Expect.isTrue(renamedFile.existsSync());
|
||||
renamedFile.deleteSync();
|
||||
}
|
||||
|
||||
void testReadWrite(String dir) {
|
||||
final path = '${dir}\\a_long_path_filename';
|
||||
Expect.isTrue(path.length > maxPath);
|
||||
final file = File(path);
|
||||
|
||||
final content = "testReadWrite";
|
||||
file.writeAsStringSync(content);
|
||||
Expect.isTrue(file.existsSync());
|
||||
|
||||
int length = file.lengthSync();
|
||||
Expect.equals(content.length, length);
|
||||
|
||||
final string = file.readAsStringSync();
|
||||
Expect.equals(content, string);
|
||||
file.deleteSync();
|
||||
}
|
||||
|
||||
void testOpen(String dir) {
|
||||
final path = '${dir}\\a_long_path_filename';
|
||||
Expect.isTrue(path.length > maxPath);
|
||||
final file = File(path);
|
||||
file.createSync();
|
||||
final access = file.openSync();
|
||||
access.closeSync();
|
||||
}
|
||||
|
||||
void testFileStat(String dir) {
|
||||
final path = '${dir}\\a_long_path_filename';
|
||||
Expect.isTrue(path.length > maxPath);
|
||||
final file = File(path);
|
||||
file.createSync();
|
||||
final stat = FileStat.statSync(file.path);
|
||||
|
||||
final dateTime = DateTime.utc(2020);
|
||||
|
||||
file.setLastModifiedSync(dateTime);
|
||||
Expect.notEquals(
|
||||
stat.modified.toString(), file.lastModifiedSync().toString());
|
||||
|
||||
file.setLastAccessedSync(dateTime);
|
||||
Expect.notEquals(
|
||||
stat.accessed.toString(), file.lastAccessedSync().toString());
|
||||
}
|
||||
|
||||
void testCreateLinkToDir(String dir) {
|
||||
final path = '${dir}\\a_long_path_linkname';
|
||||
Expect.isTrue(path.length > maxPath);
|
||||
var target = '$dir\\a_long_path_target';
|
||||
final link = Link(path)..createSync(target);
|
||||
|
||||
final dest = Directory(target)..createSync();
|
||||
Expect.isTrue(dest.existsSync());
|
||||
|
||||
Expect.isTrue(link.existsSync());
|
||||
Expect.isTrue(link.targetSync().contains('a_long_path_target'));
|
||||
|
||||
// Rename link
|
||||
var renamedLink = link.renameSync('${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 = Directory(target)..createSync();
|
||||
renamedLink.updateSync(target);
|
||||
Expect.isTrue(renamedLink.targetSync().contains('an_updated_target'));
|
||||
|
||||
dest.deleteSync();
|
||||
renamedDest.deleteSync();
|
||||
renamedLink.deleteSync();
|
||||
}
|
||||
|
||||
void testCreateLinkToFile(String dir) {
|
||||
final path = '${dir}\\a_long_path_linkname';
|
||||
Expect.isTrue(path.length > maxPath);
|
||||
var target = '$dir\\a_long_path_target';
|
||||
final link = Link(path)..createSync(target);
|
||||
|
||||
final dest = File(target)..createSync();
|
||||
Expect.isTrue(dest.existsSync());
|
||||
|
||||
Expect.isTrue(link.existsSync());
|
||||
Expect.isTrue(link.targetSync().contains('a_long_path_target'));
|
||||
Expect.isTrue(link.resolveSymbolicLinksSync().contains('a_long_path_target'));
|
||||
|
||||
// Rename link
|
||||
var renamedLink = link.renameSync('${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);
|
||||
Expect.isTrue(renamedLink.targetSync().contains('an_updated_target'));
|
||||
|
||||
dest.deleteSync();
|
||||
renamedDest.deleteSync();
|
||||
renamedLink.deleteSync();
|
||||
}
|
||||
|
||||
testNormalLinkToLongPath(String short, String long) {
|
||||
var target = File('$long\\file_target')..createSync();
|
||||
final link = Link('$short\\link')..createSync(target.path);
|
||||
Expect.isTrue(target.path.length > maxPath);
|
||||
Expect.isTrue(link.resolveSymbolicLinksSync().length > maxPath);
|
||||
Expect.isTrue(link.path.length < maxPath);
|
||||
Expect.isTrue(link.resolveSymbolicLinksSync().contains('file_target'));
|
||||
|
||||
Expect.isTrue(link.existsSync());
|
||||
Expect.equals(target.path, link.targetSync());
|
||||
|
||||
var targetDir = Directory('$long\\dir_target')..createSync();
|
||||
link.updateSync(targetDir.path);
|
||||
Expect.equals(targetDir.path, link.targetSync());
|
||||
|
||||
link.deleteSync();
|
||||
target.deleteSync();
|
||||
targetDir.deleteSync();
|
||||
}
|
||||
|
||||
testLongPathLinkToNormal(String short, String long) {
|
||||
var target = File('$short\\file_target')..createSync();
|
||||
final link = Link('$long\\link')..createSync(target.path);
|
||||
|
||||
Expect.isTrue(target.path.length < maxPath);
|
||||
Expect.isTrue(link.path.length > maxPath);
|
||||
Expect.isTrue(link.resolveSymbolicLinksSync().contains('file_target'));
|
||||
|
||||
Expect.isTrue(link.existsSync());
|
||||
Expect.equals(target.path, link.targetSync());
|
||||
|
||||
var targetDir = Directory('$short\\dir_target')..createSync();
|
||||
link.updateSync(targetDir.path);
|
||||
Expect.equals(targetDir.path, link.targetSync());
|
||||
|
||||
link.deleteSync();
|
||||
target.deleteSync();
|
||||
targetDir.deleteSync();
|
||||
}
|
||||
|
||||
testDirectorySetCurrent(String dir) {
|
||||
// This tests setting a long path directory to current directory.
|
||||
// This will fail.
|
||||
Expect.isTrue(dir.length > maxPath);
|
||||
Expect.throws<FileSystemException>(() {
|
||||
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 oldCurrent = Directory.current;
|
||||
Directory.current = tmp;
|
||||
try {
|
||||
String dir = createLongPathDir(tmp).path;
|
||||
testDirectorySetCurrent(dir);
|
||||
for (final path in [dir, ".\\$longName", ".\\$longName\\..\\$longName"]) {
|
||||
testCreate(path);
|
||||
testCopy(path);
|
||||
testRename(path);
|
||||
testReadWrite(path);
|
||||
testOpen(path);
|
||||
testFileStat(path);
|
||||
testCreateLinkToDir(path);
|
||||
testCreateLinkToFile(path);
|
||||
}
|
||||
|
||||
testNormalLinkToLongPath(tmp.path, dir);
|
||||
testLongPathLinkToNormal(tmp.path, dir);
|
||||
} finally {
|
||||
// Reset the current Directory.
|
||||
Directory.current = oldCurrent;
|
||||
tmp.deleteSync(recursive: true);
|
||||
}
|
||||
}
|
39
tests/standalone/io/file_relative_long_path_test.dart
Normal file
39
tests/standalone/io/file_relative_long_path_test.dart
Normal file
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
//
|
||||
// This test is Windows-only. It tests a short (shorter than 260) relative path
|
||||
// representing a long absolute path cannot be used by Windows API. Running this
|
||||
// test without proper support on long path will get an error.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
const maxPath = 260;
|
||||
|
||||
void main(args) {
|
||||
if (!Platform.isWindows) {
|
||||
return;
|
||||
}
|
||||
final dir = Directory.systemTemp.createTempSync('test');
|
||||
|
||||
if (dir.path.length >= maxPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure oldpath is shorter than MAX_PATH (260).
|
||||
int length = (maxPath - dir.path.length) ~/ 2;
|
||||
final oldpath = Directory('${dir.path}\\${'x' * length}}');
|
||||
oldpath.createSync(recursive: true);
|
||||
final temp = Directory.current;
|
||||
|
||||
Directory.current = oldpath.path;
|
||||
|
||||
// The length of relative path is always shorter than maxPath, but it
|
||||
// represents a path exceeding the maxPath.
|
||||
final newpath = Directory('.\\${'y' * 2 * length}');
|
||||
newpath.createSync();
|
||||
|
||||
// Reset current directory before deletion.
|
||||
Directory.current = temp.path;
|
||||
dir.deleteSync(recursive: true);
|
||||
}
|
247
tests/standalone_2/io/file_long_path_test.dart
Normal file
247
tests/standalone_2/io/file_long_path_test.dart
Normal file
|
@ -0,0 +1,247 @@
|
|||
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
//
|
||||
// This test is Windows-only.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import "package:expect/expect.dart";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void testCreate(String dir) {
|
||||
final path = '${dir}\\a_long_path_filename';
|
||||
Expect.isTrue(path.length > maxPath);
|
||||
final file = File(path);
|
||||
file.createSync();
|
||||
Expect.isTrue(file.existsSync());
|
||||
file.deleteSync();
|
||||
}
|
||||
|
||||
void testCopy(String dir) {
|
||||
final src = '${dir}\\a_long_path_filename_1';
|
||||
final dest = '${dir}\\a_long_path_filename_2';
|
||||
Expect.isTrue(src.length > maxPath);
|
||||
final file1 = File(src);
|
||||
file1.createSync();
|
||||
|
||||
final file2 = file1.copySync(dest);
|
||||
Expect.isTrue(file2.existsSync());
|
||||
file1.deleteSync();
|
||||
file2.deleteSync();
|
||||
}
|
||||
|
||||
void testRename(String dir) {
|
||||
final path = '${dir}\\a_long_path_filename';
|
||||
Expect.isTrue(path.length > maxPath);
|
||||
final file = File(path);
|
||||
file.createSync();
|
||||
Expect.isTrue(file.existsSync());
|
||||
|
||||
final renamedFile = file.renameSync('${path}_copy');
|
||||
|
||||
Expect.isFalse(file.existsSync());
|
||||
Expect.isTrue(renamedFile.existsSync());
|
||||
renamedFile.deleteSync();
|
||||
}
|
||||
|
||||
void testReadWrite(String dir) {
|
||||
final path = '${dir}\\a_long_path_filename';
|
||||
Expect.isTrue(path.length > maxPath);
|
||||
final file = File(path);
|
||||
|
||||
final content = "testReadWrite";
|
||||
file.writeAsStringSync(content);
|
||||
Expect.isTrue(file.existsSync());
|
||||
|
||||
int length = file.lengthSync();
|
||||
Expect.equals(content.length, length);
|
||||
|
||||
final string = file.readAsStringSync();
|
||||
Expect.equals(content, string);
|
||||
file.deleteSync();
|
||||
}
|
||||
|
||||
void testOpen(String dir) {
|
||||
final path = '${dir}\\a_long_path_filename';
|
||||
Expect.isTrue(path.length > maxPath);
|
||||
final file = File(path);
|
||||
file.createSync();
|
||||
final access = file.openSync();
|
||||
access.closeSync();
|
||||
}
|
||||
|
||||
void testFileStat(String dir) {
|
||||
final path = '${dir}\\a_long_path_filename';
|
||||
Expect.isTrue(path.length > maxPath);
|
||||
final file = File(path);
|
||||
file.createSync();
|
||||
final stat = FileStat.statSync(file.path);
|
||||
|
||||
final dateTime = DateTime.utc(2020);
|
||||
|
||||
file.setLastModifiedSync(dateTime);
|
||||
Expect.notEquals(
|
||||
stat.modified.toString(), file.lastModifiedSync().toString());
|
||||
|
||||
file.setLastAccessedSync(dateTime);
|
||||
Expect.notEquals(
|
||||
stat.accessed.toString(), file.lastAccessedSync().toString());
|
||||
}
|
||||
|
||||
void testCreateLinkToDir(String dir) {
|
||||
final path = '${dir}\\a_long_path_linkname';
|
||||
Expect.isTrue(path.length > maxPath);
|
||||
var target = '$dir\\a_long_path_target';
|
||||
final link = Link(path)..createSync(target);
|
||||
|
||||
final dest = Directory(target)..createSync();
|
||||
Expect.isTrue(dest.existsSync());
|
||||
|
||||
Expect.isTrue(link.existsSync());
|
||||
Expect.isTrue(link.targetSync().contains('a_long_path_target'));
|
||||
|
||||
// Rename link
|
||||
var renamedLink = link.renameSync('${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 = Directory(target)..createSync();
|
||||
renamedLink.updateSync(target);
|
||||
Expect.isTrue(renamedLink.targetSync().contains('an_updated_target'));
|
||||
|
||||
dest.deleteSync();
|
||||
renamedDest.deleteSync();
|
||||
renamedLink.deleteSync();
|
||||
}
|
||||
|
||||
void testCreateLinkToFile(String dir) {
|
||||
final path = '${dir}\\a_long_path_linkname';
|
||||
Expect.isTrue(path.length > maxPath);
|
||||
var target = '$dir\\a_long_path_target';
|
||||
final link = Link(path)..createSync(target);
|
||||
|
||||
final dest = File(target)..createSync();
|
||||
Expect.isTrue(dest.existsSync());
|
||||
|
||||
Expect.isTrue(link.existsSync());
|
||||
Expect.isTrue(link.targetSync().contains('a_long_path_target'));
|
||||
Expect.isTrue(link.resolveSymbolicLinksSync().contains('a_long_path_target'));
|
||||
|
||||
// Rename link
|
||||
var renamedLink = link.renameSync('${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);
|
||||
Expect.isTrue(renamedLink.targetSync().contains('an_updated_target'));
|
||||
|
||||
dest.deleteSync();
|
||||
renamedDest.deleteSync();
|
||||
renamedLink.deleteSync();
|
||||
}
|
||||
|
||||
testNormalLinkToLongPath(String short, String long) {
|
||||
var target = File('$long\\file_target')..createSync();
|
||||
final link = Link('$short\\link')..createSync(target.path);
|
||||
Expect.isTrue(target.path.length > maxPath);
|
||||
Expect.isTrue(link.resolveSymbolicLinksSync().length > maxPath);
|
||||
Expect.isTrue(link.path.length < maxPath);
|
||||
Expect.isTrue(link.resolveSymbolicLinksSync().contains('file_target'));
|
||||
|
||||
Expect.isTrue(link.existsSync());
|
||||
Expect.equals(target.path, link.targetSync());
|
||||
|
||||
var targetDir = Directory('$long\\dir_target')..createSync();
|
||||
link.updateSync(targetDir.path);
|
||||
Expect.equals(targetDir.path, link.targetSync());
|
||||
|
||||
link.deleteSync();
|
||||
target.deleteSync();
|
||||
targetDir.deleteSync();
|
||||
}
|
||||
|
||||
testLongPathLinkToNormal(String short, String long) {
|
||||
var target = File('$short\\file_target')..createSync();
|
||||
final link = Link('$long\\link')..createSync(target.path);
|
||||
|
||||
Expect.isTrue(target.path.length < maxPath);
|
||||
Expect.isTrue(link.path.length > maxPath);
|
||||
Expect.isTrue(link.resolveSymbolicLinksSync().contains('file_target'));
|
||||
|
||||
Expect.isTrue(link.existsSync());
|
||||
Expect.equals(target.path, link.targetSync());
|
||||
|
||||
var targetDir = Directory('$short\\dir_target')..createSync();
|
||||
link.updateSync(targetDir.path);
|
||||
Expect.equals(targetDir.path, link.targetSync());
|
||||
|
||||
link.deleteSync();
|
||||
target.deleteSync();
|
||||
targetDir.deleteSync();
|
||||
}
|
||||
|
||||
testDirectorySetCurrent(String dir) {
|
||||
// This tests setting a long path directory to current directory.
|
||||
// This will fail.
|
||||
Expect.isTrue(dir.length > maxPath);
|
||||
Expect.throws<FileSystemException>(() {
|
||||
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 oldCurrent = Directory.current;
|
||||
Directory.current = tmp;
|
||||
try {
|
||||
String dir = createLongPathDir(tmp).path;
|
||||
testDirectorySetCurrent(dir);
|
||||
for (final path in [dir, ".\\$longName", ".\\$longName\\..\\$longName"]) {
|
||||
testCreate(path);
|
||||
testCopy(path);
|
||||
testRename(path);
|
||||
testReadWrite(path);
|
||||
testOpen(path);
|
||||
testFileStat(path);
|
||||
testCreateLinkToDir(path);
|
||||
testCreateLinkToFile(path);
|
||||
}
|
||||
|
||||
testNormalLinkToLongPath(tmp.path, dir);
|
||||
testLongPathLinkToNormal(tmp.path, dir);
|
||||
} finally {
|
||||
// Reset the current Directory.
|
||||
Directory.current = oldCurrent;
|
||||
tmp.deleteSync(recursive: true);
|
||||
}
|
||||
}
|
39
tests/standalone_2/io/file_relative_long_path_test.dart
Normal file
39
tests/standalone_2/io/file_relative_long_path_test.dart
Normal file
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
//
|
||||
// This test is Windows-only. It tests a short (shorter than 260) relative path
|
||||
// representing a long absolute path cannot be used by Windows API. Running this
|
||||
// test without proper support on long path will get an error.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
const maxPath = 260;
|
||||
|
||||
void main(args) {
|
||||
if (!Platform.isWindows) {
|
||||
return;
|
||||
}
|
||||
final dir = Directory.systemTemp.createTempSync('test');
|
||||
|
||||
if (dir.path.length >= maxPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure oldpath is shorter than MAX_PATH (260).
|
||||
int length = (maxPath - dir.path.length) ~/ 2;
|
||||
final oldpath = Directory('${dir.path}\\${'x' * length}}');
|
||||
oldpath.createSync(recursive: true);
|
||||
final temp = Directory.current;
|
||||
|
||||
Directory.current = oldpath.path;
|
||||
|
||||
// The length of relative path is always shorter than maxPath, but it
|
||||
// represents a path exceeding the maxPath.
|
||||
final newpath = Directory('.\\${'y' * 2 * length}');
|
||||
newpath.createSync();
|
||||
|
||||
// Reset current directory before deletion.
|
||||
Directory.current = temp.path;
|
||||
dir.deleteSync(recursive: true);
|
||||
}
|
Loading…
Reference in a new issue