[io/win] Always use absolute, canonicalized paths.
This ensures that on Windows we take advantage of 32k-character file name limit consistently for all files and directories operations. TEST=ci BUG=https://github.com/dart-lang/sdk/issues/56125 BUG=https://github.com/dart-lang/sdk/issues/55972 CoreLibraryReviewExempt: adds windows-specific error code for invalid file name Change-Id: I83bd3ceac579e589469e47b2cf5216db457cae8b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/375421 Reviewed-by: Slava Egorov <vegorov@google.com> Commit-Queue: Alexander Aprelev <aam@google.com>
This commit is contained in:
committed by
Commit Queue
parent
ca77beac58
commit
f9c603e76b
@@ -379,7 +379,7 @@ Directory::ExistsResult Directory::Exists(const wchar_t* dir_name) {
|
||||
|
||||
Directory::ExistsResult Directory::Exists(Namespace* namespc,
|
||||
const char* dir_name) {
|
||||
const auto path = ToWinAPIDirectoryPath(dir_name);
|
||||
const auto path = ToWinAPIPath(dir_name);
|
||||
return Exists(path.get());
|
||||
}
|
||||
|
||||
@@ -400,7 +400,7 @@ char* Directory::CurrentNoScope() {
|
||||
}
|
||||
|
||||
bool Directory::Create(Namespace* namespc, const char* dir_name) {
|
||||
const auto path = ToWinAPIDirectoryPath(dir_name);
|
||||
const auto path = ToWinAPIPath(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) &&
|
||||
@@ -506,8 +506,7 @@ const char* Directory::CreateTemp(Namespace* namespc, const char* prefix) {
|
||||
bool Directory::Delete(Namespace* namespc,
|
||||
const char* dir_name,
|
||||
bool recursive) {
|
||||
const auto path =
|
||||
ToWinAPIDirectoryPath(dir_name, /*force_long_prefix=*/recursive);
|
||||
const auto path = ToWinAPIPath(dir_name);
|
||||
bool result = false;
|
||||
if (!recursive) {
|
||||
if (File::GetType(path.get(), /*follow_links=*/true) ==
|
||||
@@ -526,13 +525,13 @@ bool Directory::Delete(Namespace* namespc,
|
||||
bool Directory::Rename(Namespace* namespc,
|
||||
const char* old_name,
|
||||
const char* new_name) {
|
||||
const auto old_path = ToWinAPIDirectoryPath(old_name);
|
||||
const auto old_path = ToWinAPIPath(old_name);
|
||||
ExistsResult exists = Exists(old_path.get());
|
||||
if (exists != EXISTS) {
|
||||
SetLastError(ERROR_FILE_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
const auto new_path = ToWinAPIDirectoryPath(new_name);
|
||||
const auto new_path = ToWinAPIPath(new_name);
|
||||
DWORD flags = MOVEFILE_WRITE_THROUGH;
|
||||
int move_status = MoveFileExW(old_path.get(), new_path.get(), flags);
|
||||
return (move_status != 0);
|
||||
|
||||
+39
-121
@@ -379,115 +379,33 @@ static bool IsDeviceNamespacePrefixed(const std::unique_ptr<wchar_t[]>& path) {
|
||||
kDeviceNamespacePrefixLength) == 0;
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Converts the given UTF8 path to wide char '\\?\'-prefix absolute path.
|
||||
//
|
||||
// This function does not change paths which are already prefixed with `\\.\`
|
||||
// prefix.
|
||||
//
|
||||
// UNC paths (`\\server\share\...`) are converted to `\\?\UNC\server\share\...`
|
||||
// if necessary.
|
||||
//
|
||||
// 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<wchar_t[]> ToWinAPIPath(const char* utf8_path,
|
||||
bool is_file,
|
||||
bool force_long_prefix) {
|
||||
// Note that 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.
|
||||
std::unique_ptr<wchar_t[]> ToWinAPIPath(const char* utf8_path) {
|
||||
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;
|
||||
|
||||
std::unique_ptr<wchar_t[]> absolute_path;
|
||||
// Need to convert to absolute path if we want to use long prefix
|
||||
// to normalize the path - this is to get rid of .., . that can't be
|
||||
// interpreted in the long-prefixed mode).
|
||||
if (force_long_prefix || !IsAbsolutePath(path.get())) {
|
||||
absolute_path = ConvertToAbsolutePath(path);
|
||||
if (absolute_path == nullptr) {
|
||||
return path;
|
||||
}
|
||||
} else {
|
||||
absolute_path = std::move(path);
|
||||
// Among other things ConvertToAbsolutePath replaces '/' with '\',
|
||||
// which PathAllocCanonicalize won't do.
|
||||
auto abs_path = ConvertToAbsolutePath(path);
|
||||
if (abs_path.get() == nullptr) {
|
||||
return std::unique_ptr<wchar_t[]>(nullptr);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
PWSTR canonical_path;
|
||||
if (PathAllocCanonicalize(abs_path.get(),
|
||||
PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH,
|
||||
&canonical_path) != S_OK) {
|
||||
return std::unique_ptr<wchar_t[]>(nullptr);
|
||||
}
|
||||
|
||||
if (IsLongPathPrefixed(absolute_path) ||
|
||||
IsDeviceNamespacePrefixed(absolute_path)) {
|
||||
return absolute_path;
|
||||
}
|
||||
|
||||
// If the path already starts with `\\` but not with `\\?\` or `\\.\`
|
||||
// then we need to replace `\\` with `\\?\UNC\`.
|
||||
const bool is_unc = (wcsncmp(absolute_path.get(), L"\\\\", 2) == 0);
|
||||
const wchar_t* kUNCLongPathPrefix = L"\\\\?\\UNC\\";
|
||||
const int kUNCLongPathPrefixLength = 8;
|
||||
|
||||
// Add prefix and replace forward slashes with backward slashes.
|
||||
//
|
||||
// If the path is UNC we skip the first two characters of the path `\\`
|
||||
// hence -2.
|
||||
const intptr_t result_length =
|
||||
(is_unc ? kUNCLongPathPrefixLength : kLongPathPrefixLength) +
|
||||
path_length + (is_unc ? -2 : 0) + 1;
|
||||
auto result = std::make_unique<wchar_t[]>(result_length);
|
||||
intptr_t result_pos;
|
||||
intptr_t path_pos;
|
||||
if (is_unc) {
|
||||
wcsncpy(result.get(), kUNCLongPathPrefix, kUNCLongPathPrefixLength);
|
||||
result_pos = kUNCLongPathPrefixLength;
|
||||
path_pos = 2;
|
||||
} else {
|
||||
wcsncpy(result.get(), kLongPathPrefix, kLongPathPrefixLength);
|
||||
result_pos = kLongPathPrefixLength;
|
||||
path_pos = 0;
|
||||
}
|
||||
while (path_pos < path_length) {
|
||||
wchar_t ch = absolute_path[path_pos++];
|
||||
result.get()[result_pos++] = ch == L'/' ? L'\\' : ch;
|
||||
}
|
||||
result.get()[result_pos++] = L'\0';
|
||||
ASSERT(result_pos == result_length);
|
||||
auto result = std::unique_ptr<wchar_t[]>(wcsdup(canonical_path));
|
||||
LocalFree(canonical_path);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 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<wchar_t[]> ToWinAPIFilePath(
|
||||
const char* path,
|
||||
bool force_long_prefix = false) {
|
||||
return ToWinAPIPath(path, /*is_file=*/true, force_long_prefix);
|
||||
}
|
||||
|
||||
std::unique_ptr<wchar_t[]> ToWinAPIDirectoryPath(
|
||||
const char* path,
|
||||
bool force_long_prefix /* = false */) {
|
||||
return ToWinAPIPath(path, /*is_file=*/false, force_long_prefix);
|
||||
}
|
||||
|
||||
File* File::Open(Namespace* namespc, const char* name, FileOpenMode mode) {
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
const auto path = ToWinAPIPath(name);
|
||||
File* file = FileOpenW(path.get(), mode);
|
||||
return file;
|
||||
}
|
||||
@@ -557,7 +475,7 @@ static bool FileExists(const wchar_t* path) {
|
||||
}
|
||||
|
||||
bool File::Exists(Namespace* namespc, const char* name) {
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
const auto path = ToWinAPIPath(name);
|
||||
return FileExists(path.get());
|
||||
}
|
||||
|
||||
@@ -571,7 +489,7 @@ bool File::ExistsUri(Namespace* namespc, const char* uri) {
|
||||
}
|
||||
|
||||
bool File::Create(Namespace* namespc, const char* name, bool exclusive) {
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
const auto path = ToWinAPIPath(name);
|
||||
int flags = O_RDONLY | O_CREAT;
|
||||
if (exclusive) {
|
||||
flags |= O_EXCL;
|
||||
@@ -616,12 +534,12 @@ typedef struct _REPARSE_DATA_BUFFER {
|
||||
bool File::CreateLink(Namespace* namespc,
|
||||
const char* utf8_name,
|
||||
const char* utf8_target) {
|
||||
const auto name = ToWinAPIFilePath(utf8_name);
|
||||
const auto name = ToWinAPIPath(utf8_name);
|
||||
|
||||
std::unique_ptr<wchar_t[]> target;
|
||||
bool target_is_directory;
|
||||
if (File::IsAbsolutePath(utf8_target)) {
|
||||
target = ToWinAPIFilePath(utf8_target);
|
||||
target = ToWinAPIPath(utf8_target);
|
||||
target_is_directory =
|
||||
File::GetType(target.get(), /*follow_links=*/true) == kIsDirectory;
|
||||
} else {
|
||||
@@ -706,7 +624,7 @@ bool File::CreatePipe(Namespace* namespc, File** readPipe, File** writePipe) {
|
||||
}
|
||||
|
||||
bool File::Delete(Namespace* namespc, const char* name) {
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
const auto path = ToWinAPIPath(name);
|
||||
int status = _wremove(path.get());
|
||||
return status != -1;
|
||||
}
|
||||
@@ -731,20 +649,20 @@ static bool DeleteLinkHelper(const wchar_t* path) {
|
||||
}
|
||||
|
||||
bool File::DeleteLink(Namespace* namespc, const char* name) {
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
const auto path = ToWinAPIPath(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);
|
||||
const auto old_path = ToWinAPIPath(old_name);
|
||||
File::Type type = File::GetType(old_path.get(), /*follow_links=*/false);
|
||||
if (type != expected) {
|
||||
SetLastError(ERROR_FILE_NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
const auto new_path = ToWinAPIFilePath(new_name);
|
||||
const auto new_path = ToWinAPIPath(new_name);
|
||||
DWORD flags = MOVEFILE_WRITE_THROUGH | MOVEFILE_REPLACE_EXISTING;
|
||||
|
||||
// Symbolic links (e.g. produced by Link.create) to directories on Windows
|
||||
@@ -860,8 +778,8 @@ bool File::Copy(Namespace* namespc,
|
||||
const char* new_name) {
|
||||
// We are going to concatenate new path with temporary file names in
|
||||
// CopyIntoTempFile so we force long prefix no matter what.
|
||||
const auto old_path = ToWinAPIFilePath(old_name);
|
||||
const auto new_path = ToWinAPIFilePath(new_name, /*force_long_prefix=*/true);
|
||||
const auto old_path = ToWinAPIPath(old_name);
|
||||
const auto new_path = ToWinAPIPath(new_name);
|
||||
|
||||
File::Type type = GetType(old_path.get(), /*follow_links=*/false);
|
||||
if (type != kIsFile) {
|
||||
@@ -893,7 +811,7 @@ bool File::Copy(Namespace* namespc,
|
||||
|
||||
int64_t File::LengthFromPath(Namespace* namespc, const char* name) {
|
||||
struct __stat64 st;
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
const auto path = ToWinAPIPath(name);
|
||||
if (!StatHelper(path.get(), &st)) {
|
||||
return -1;
|
||||
}
|
||||
@@ -904,7 +822,7 @@ const char* File::LinkTarget(Namespace* namespc,
|
||||
const char* pathname,
|
||||
char* dest,
|
||||
int dest_size) {
|
||||
const auto path = ToWinAPIFilePath(pathname);
|
||||
const auto path = ToWinAPIPath(pathname);
|
||||
HANDLE dir_handle = CreateFileW(
|
||||
path.get(), GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
|
||||
@@ -991,7 +909,7 @@ const char* File::LinkTarget(Namespace* namespc,
|
||||
}
|
||||
|
||||
void File::Stat(Namespace* namespc, const char* name, int64_t* data) {
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
const auto path = ToWinAPIPath(name);
|
||||
File::Type type = GetType(path.get(), /*follow_links=*/true);
|
||||
data[kType] = type;
|
||||
if (type != kDoesNotExist) {
|
||||
@@ -1011,7 +929,7 @@ void File::Stat(Namespace* namespc, const char* name, int64_t* data) {
|
||||
|
||||
time_t File::LastAccessed(Namespace* namespc, const char* name) {
|
||||
struct __stat64 st;
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
const auto path = ToWinAPIPath(name);
|
||||
if (!StatHelper(path.get(), &st)) {
|
||||
return -1;
|
||||
}
|
||||
@@ -1020,7 +938,7 @@ time_t File::LastAccessed(Namespace* namespc, const char* name) {
|
||||
|
||||
time_t File::LastModified(Namespace* namespc, const char* name) {
|
||||
struct __stat64 st;
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
const auto path = ToWinAPIPath(name);
|
||||
if (!StatHelper(path.get(), &st)) {
|
||||
return -1;
|
||||
}
|
||||
@@ -1031,7 +949,7 @@ bool File::SetLastAccessed(Namespace* namespc,
|
||||
const char* name,
|
||||
int64_t millis) {
|
||||
struct __stat64 st;
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
const auto path = ToWinAPIPath(name);
|
||||
if (!StatHelper(path.get(), &st)) { // Checks that it is a file.
|
||||
return false;
|
||||
}
|
||||
@@ -1060,7 +978,7 @@ bool File::SetLastModified(Namespace* namespc,
|
||||
int64_t millis) {
|
||||
// First get the current times.
|
||||
struct __stat64 st;
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
const auto path = ToWinAPIPath(name);
|
||||
if (!StatHelper(path.get(), &st)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1089,7 +1007,7 @@ const char* File::GetCanonicalPath(Namespace* namespc,
|
||||
const char* pathname,
|
||||
char* dest,
|
||||
int dest_size) {
|
||||
const auto path = ToWinAPIFilePath(pathname);
|
||||
const auto path = ToWinAPIPath(pathname);
|
||||
HANDLE file_handle =
|
||||
CreateFileW(path.get(), 0, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||
@@ -1191,7 +1109,7 @@ File::Type File::GetType(const wchar_t* path, bool follow_links) {
|
||||
File::Type File::GetType(Namespace* namespc,
|
||||
const char* name,
|
||||
bool follow_links) {
|
||||
const auto path = ToWinAPIFilePath(name);
|
||||
const auto path = ToWinAPIPath(name);
|
||||
return GetType(path.get(), follow_links);
|
||||
}
|
||||
|
||||
@@ -1202,8 +1120,8 @@ File::Identical File::AreIdentical(Namespace* namespc_1,
|
||||
USE(namespc_1);
|
||||
USE(namespc_2);
|
||||
BY_HANDLE_FILE_INFORMATION file_info[2];
|
||||
const std::unique_ptr<wchar_t[]> file_names[2] = {ToWinAPIFilePath(file_1),
|
||||
ToWinAPIFilePath(file_2)};
|
||||
const std::unique_ptr<wchar_t[]> file_names[2] = {ToWinAPIPath(file_1),
|
||||
ToWinAPIPath(file_2)};
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
HANDLE file_handle = CreateFileW(
|
||||
file_names[i].get(), 0,
|
||||
|
||||
+5
-11
@@ -16,18 +16,12 @@
|
||||
namespace dart {
|
||||
namespace bin {
|
||||
|
||||
// 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.
|
||||
// Converts the given UTF8 path to wide char '\\?\'-prefix absolute path.
|
||||
//
|
||||
// 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<wchar_t[]> ToWinAPIDirectoryPath(
|
||||
const char* path,
|
||||
bool force_long_prefix = false);
|
||||
// Note that 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.
|
||||
std::unique_ptr<wchar_t[]> ToWinAPIPath(const char* path);
|
||||
|
||||
} // namespace bin
|
||||
} // namespace dart
|
||||
|
||||
@@ -39,6 +39,7 @@ const _errorNetworkAccessDenied = 65;
|
||||
const _errorBadNetName = 67;
|
||||
const _errorFileExists = 80;
|
||||
const _errorDriveLocked = 108;
|
||||
const _errorInvalidName = 123;
|
||||
const _errorBadPathName = 161;
|
||||
const _errorAlreadyExists = 183;
|
||||
const _errorFilenameExedRange = 206;
|
||||
|
||||
@@ -173,6 +173,13 @@ class FileLock {
|
||||
/// In addition to length, the [exists], [lastModified], [stat], and
|
||||
/// other methods, are asynchronous.
|
||||
///
|
||||
/// ## Special 'nul' file
|
||||
///
|
||||
/// On Linux and Mac '/dev/null' and on Windows '\\?\NUL' refer to a special file,
|
||||
/// such that all writes to it get consumed and disappear, and all reads produce empty
|
||||
/// output. Note that on Windows 'nul'(without '\\?\'-prefix) refers to a regular file
|
||||
/// named 'nul' in current directory.
|
||||
///
|
||||
/// ## Other resources
|
||||
///
|
||||
/// * The [Files and directories](https://dart.dev/guides/libraries/library-tour#files-and-directories)
|
||||
@@ -1063,6 +1070,7 @@ class FileSystemException implements IOException {
|
||||
case _errorFileNotFound:
|
||||
case _errorPathNotFound:
|
||||
case _errorInvalidDrive:
|
||||
case _errorInvalidName:
|
||||
case _errorNoMoreFiles:
|
||||
case _errorBadNetpath:
|
||||
case _errorBadNetName:
|
||||
|
||||
@@ -188,10 +188,20 @@ class DirectoryTest {
|
||||
buffer.write("/../${subDirName}");
|
||||
}
|
||||
var long = new Directory("${buffer.toString()}");
|
||||
Future<void>.value(long.delete()).catchError((error) {
|
||||
Expect.isTrue(error is FileSystemException);
|
||||
asyncEnd();
|
||||
});
|
||||
// Passes on Windows, fails everywhere else.
|
||||
// Windows can handle path names up to 32k in length(provided that
|
||||
// they are '\\?\'-prefixed, which they are internally in dart io),
|
||||
// which is more than Linux or Mac can handle.
|
||||
if (Platform.isWindows) {
|
||||
long.delete().then((_) {
|
||||
asyncEnd();
|
||||
});
|
||||
} else {
|
||||
Future<void>.value(long.delete()).catchError((error) {
|
||||
Expect.isTrue(error is FileSystemException);
|
||||
asyncEnd();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -250,7 +260,12 @@ class DirectoryTest {
|
||||
Directory d = Directory.systemTemp.createTempSync('dart_directory_test');
|
||||
StringBuffer buffer = createLongDirName(d);
|
||||
var long = new Directory("${buffer.toString()}");
|
||||
Expect.throws(long.deleteSync);
|
||||
if (!Platform.isWindows) {
|
||||
// Windows can handle path names up to 32k in length(provided that
|
||||
// they are '\\?\'-prefixed, which they are internally in dart io),
|
||||
// which is more than Linux or Mac can handle.
|
||||
Expect.throws(long.deleteSync);
|
||||
}
|
||||
d.deleteSync(recursive: true);
|
||||
}
|
||||
{
|
||||
|
||||
@@ -23,7 +23,7 @@ bool checkCannotOpenFileException(e) {
|
||||
} else if (Platform.operatingSystem == "macos") {
|
||||
Expect.equals(2, e.osError.errorCode);
|
||||
} else if (Platform.operatingSystem == "windows") {
|
||||
Expect.equals(3, e.osError.errorCode);
|
||||
Expect.equals(123, e.osError.errorCode);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -8,12 +8,8 @@ import "package:expect/expect.dart";
|
||||
|
||||
/// Regression test for https://github.com/dart-lang/sdk/issues/54386.
|
||||
void testDriveLetterStat() {
|
||||
// "C:" not acceptable
|
||||
final cDrive = Directory("C:");
|
||||
final cDriveStat = cDrive.statSync();
|
||||
Expect.equals(cDriveStat.type, FileSystemEntityType.notFound);
|
||||
// These are acceptable cases
|
||||
final acceptablePathRootDrives = ["C: ", "C:\\", "C:/"];
|
||||
final acceptablePathRootDrives = ["C:", "C: ", "C:\\", "C:/"];
|
||||
for (final drivePath in acceptablePathRootDrives) {
|
||||
final dir = Directory(drivePath);
|
||||
final dirStat = dir.statSync();
|
||||
@@ -21,6 +17,35 @@ void testDriveLetterStat() {
|
||||
}
|
||||
}
|
||||
|
||||
// Check that "C:abc" refers
|
||||
// either to a file at current directory if current directory is at "C:" drive,
|
||||
// or to a file at "C:\\abc", if current directory is not at "C:" drive.
|
||||
//
|
||||
// Do this check by looking at system temp directory entries, converting them
|
||||
// to no-backslash-after-drive-letter form, confirming that original and
|
||||
// converted refer to the same thing.
|
||||
void testDriveLetterNoBackslash() {
|
||||
final current = Directory.current.path;
|
||||
for (var e in Directory.systemTemp.listSync()) {
|
||||
final path = e.path;
|
||||
if (path.length < 3) return;
|
||||
if (path[1] == ':' && path[2] == "\\") {
|
||||
final driveletter = path[0];
|
||||
var noBackslash = path.substring(0, 2);
|
||||
if (path.substring(0, 3).compareTo(current.substring(0, 3)) == 0) {
|
||||
for (int i = 0; i < current.length; i++) {
|
||||
if (current[i] == '\\') {
|
||||
noBackslash += "..\\";
|
||||
}
|
||||
}
|
||||
}
|
||||
noBackslash += path.substring(3);
|
||||
Expect.equals("${Directory(noBackslash).statSync()}",
|
||||
"${Directory(path).statSync()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void testDeleteLongPathPrefix() {
|
||||
var dir = Directory.systemTemp.createTempSync('dart_file_win');
|
||||
var dirPath = "\\\\?\\${dir.path}";
|
||||
@@ -41,4 +66,5 @@ void main() {
|
||||
if (!Platform.isWindows) return;
|
||||
testDeleteLongPathPrefix();
|
||||
testDriveLetterStat();
|
||||
testDriveLetterNoBackslash();
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ testWriteWithLargeList(dir) {
|
||||
return;
|
||||
}
|
||||
if (Platform.isWindows) {
|
||||
File('NUL').writeAsBytesSync(bytes);
|
||||
File('\\\\?\\NUL').writeAsBytesSync(bytes);
|
||||
} else {
|
||||
File('/dev/null').writeAsBytesSync(bytes);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user