mirror of
https://github.com/dart-lang/sdk
synced 2024-10-03 00:45:16 +00:00
[ VM / dart:io ] Updated Link
implementation for Windows to use actual symbolic links.
Change-Id: I22a598e7c1f249d9150cc5ceee96daa0291e753e Reviewed-on: https://dart-review.googlesource.com/c/90362 Reviewed-by: Zach Anderson <zra@google.com> Reviewed-by: Siva Annamalai <asiva@google.com> Commit-Queue: Ben Konyi <bkonyi@google.com>
This commit is contained in:
parent
09e6a689c2
commit
b625926038
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -47,6 +47,18 @@
|
||||||
* **Breaking change:** The `klass` getter on the `InstanceConstant` class in
|
* **Breaking change:** The `klass` getter on the `InstanceConstant` class in
|
||||||
the Kernel AST API has been renamed to `classNode` for consistency.
|
the Kernel AST API has been renamed to `classNode` for consistency.
|
||||||
|
|
||||||
|
* **Breaking change:** Updated `Link` implementation to utilize true symbolic
|
||||||
|
links instead of junctions on Windows. Existing junctions will continue to
|
||||||
|
work with the new `Link` implementation, but all new links will create
|
||||||
|
symbolic links.
|
||||||
|
|
||||||
|
To create a symbolic link, Dart must be run with
|
||||||
|
administrative privileges or Developer Mode must be enabled, otherwise a
|
||||||
|
`FileSystemException` will be raised with errno set to
|
||||||
|
`ERROR_PRIVILEGE_NOT_HELD` (Issue [33966]).
|
||||||
|
|
||||||
|
[33966]: https://github.com/dart-lang/sdk/issues/33966
|
||||||
|
|
||||||
### Dart VM
|
### Dart VM
|
||||||
|
|
||||||
### Tool Changes
|
### Tool Changes
|
||||||
|
|
|
@ -375,63 +375,32 @@ typedef struct _REPARSE_DATA_BUFFER {
|
||||||
static const int kReparseDataHeaderSize = sizeof ULONG + 2 * sizeof USHORT;
|
static const int kReparseDataHeaderSize = sizeof ULONG + 2 * sizeof USHORT;
|
||||||
static const int kMountPointHeaderSize = 4 * sizeof USHORT;
|
static const int kMountPointHeaderSize = 4 * sizeof USHORT;
|
||||||
|
|
||||||
|
// Note: CreateLink used to create junctions on Windows instead of true
|
||||||
|
// symbolic links. All File::*Link methods now support handling links created
|
||||||
|
// as junctions and symbolic links.
|
||||||
bool File::CreateLink(Namespace* namespc,
|
bool File::CreateLink(Namespace* namespc,
|
||||||
const char* utf8_name,
|
const char* utf8_name,
|
||||||
const char* utf8_target) {
|
const char* utf8_target) {
|
||||||
Utf8ToWideScope name(utf8_name);
|
Utf8ToWideScope name(utf8_name);
|
||||||
int create_status = CreateDirectoryW(name.wide(), NULL);
|
|
||||||
// If the directory already existed, treat it as a success.
|
|
||||||
if ((create_status == 0) &&
|
|
||||||
((GetLastError() != ERROR_ALREADY_EXISTS) ||
|
|
||||||
((GetFileAttributesW(name.wide()) & FILE_ATTRIBUTE_DIRECTORY) != 0))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
HANDLE dir_handle = CreateFileW(
|
|
||||||
name.wide(), GENERIC_READ | GENERIC_WRITE,
|
|
||||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
|
|
||||||
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
|
|
||||||
NULL);
|
|
||||||
if (dir_handle == INVALID_HANDLE_VALUE) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Utf8ToWideScope target(utf8_target);
|
Utf8ToWideScope target(utf8_target);
|
||||||
int target_len = wcslen(target.wide());
|
DWORD flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
|
||||||
if (target_len > MAX_PATH - 1) {
|
|
||||||
CloseHandle(dir_handle);
|
File::Type type = File::GetType(namespc, utf8_target, true);
|
||||||
return false;
|
if (type == kIsDirectory) {
|
||||||
|
flags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
int reparse_data_buffer_size =
|
int create_status = CreateSymbolicLinkW(name.wide(), target.wide(), flags);
|
||||||
sizeof REPARSE_DATA_BUFFER + 2 * MAX_PATH * sizeof WCHAR;
|
|
||||||
REPARSE_DATA_BUFFER* reparse_data_buffer =
|
// If running on a Windows 10 build older than 14972, an invalid parameter
|
||||||
reinterpret_cast<REPARSE_DATA_BUFFER*>(malloc(reparse_data_buffer_size));
|
// error will be returned when trying to use the
|
||||||
reparse_data_buffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
|
// SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE flag. Retry without the flag.
|
||||||
wcscpy(reparse_data_buffer->MountPointReparseBuffer.PathBuffer,
|
if ((create_status == 0) && (GetLastError() == ERROR_INVALID_PARAMETER)) {
|
||||||
target.wide());
|
flags &= ~SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
|
||||||
wcscpy(
|
create_status = CreateSymbolicLinkW(name.wide(), target.wide(), flags);
|
||||||
reparse_data_buffer->MountPointReparseBuffer.PathBuffer + target_len + 1,
|
|
||||||
target.wide());
|
|
||||||
reparse_data_buffer->MountPointReparseBuffer.SubstituteNameOffset = 0;
|
|
||||||
reparse_data_buffer->MountPointReparseBuffer.SubstituteNameLength =
|
|
||||||
target_len * sizeof WCHAR;
|
|
||||||
reparse_data_buffer->MountPointReparseBuffer.PrintNameOffset =
|
|
||||||
(target_len + 1) * sizeof WCHAR;
|
|
||||||
reparse_data_buffer->MountPointReparseBuffer.PrintNameLength =
|
|
||||||
target_len * sizeof WCHAR;
|
|
||||||
reparse_data_buffer->ReparseDataLength =
|
|
||||||
(target_len + 1) * 2 * sizeof WCHAR + kMountPointHeaderSize;
|
|
||||||
DWORD dummy_received_bytes;
|
|
||||||
int result = DeviceIoControl(
|
|
||||||
dir_handle, FSCTL_SET_REPARSE_POINT, reparse_data_buffer,
|
|
||||||
reparse_data_buffer->ReparseDataLength + kReparseDataHeaderSize, NULL, 0,
|
|
||||||
&dummy_received_bytes, NULL);
|
|
||||||
free(reparse_data_buffer);
|
|
||||||
if (CloseHandle(dir_handle) == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return (result != 0);
|
|
||||||
|
return (create_status != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool File::Delete(Namespace* namespc, const char* name) {
|
bool File::Delete(Namespace* namespc, const char* name) {
|
||||||
|
@ -444,12 +413,18 @@ bool File::DeleteLink(Namespace* namespc, const char* name) {
|
||||||
Utf8ToWideScope system_name(name);
|
Utf8ToWideScope system_name(name);
|
||||||
bool result = false;
|
bool result = false;
|
||||||
DWORD attributes = GetFileAttributesW(system_name.wide());
|
DWORD attributes = GetFileAttributesW(system_name.wide());
|
||||||
if ((attributes != INVALID_FILE_ATTRIBUTES) &&
|
if ((attributes == INVALID_FILE_ATTRIBUTES) ||
|
||||||
(attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
|
((attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0)) {
|
||||||
// It's a junction(link), delete it.
|
SetLastError(ERROR_NOT_A_REPARSE_POINT);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
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(system_name.wide()) != 0);
|
||||||
} else {
|
} else {
|
||||||
SetLastError(ERROR_NOT_A_REPARSE_POINT);
|
// Symbolic link to a file. Remove the file.
|
||||||
|
result = (DeleteFileW(system_name.wide()) != 0);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -458,65 +433,59 @@ bool File::Rename(Namespace* namespc,
|
||||||
const char* old_path,
|
const char* old_path,
|
||||||
const char* new_path) {
|
const char* new_path) {
|
||||||
File::Type type = GetType(namespc, old_path, false);
|
File::Type type = GetType(namespc, old_path, false);
|
||||||
if (type == kIsFile) {
|
if (type != kIsFile) {
|
||||||
Utf8ToWideScope system_old_path(old_path);
|
|
||||||
Utf8ToWideScope system_new_path(new_path);
|
|
||||||
DWORD flags = MOVEFILE_WRITE_THROUGH | MOVEFILE_REPLACE_EXISTING;
|
|
||||||
int move_status =
|
|
||||||
MoveFileExW(system_old_path.wide(), system_new_path.wide(), flags);
|
|
||||||
return (move_status != 0);
|
|
||||||
} else {
|
|
||||||
SetLastError(ERROR_FILE_NOT_FOUND);
|
SetLastError(ERROR_FILE_NOT_FOUND);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
Utf8ToWideScope system_old_path(old_path);
|
||||||
|
Utf8ToWideScope system_new_path(new_path);
|
||||||
|
DWORD flags = MOVEFILE_WRITE_THROUGH | MOVEFILE_REPLACE_EXISTING;
|
||||||
|
int move_status =
|
||||||
|
MoveFileExW(system_old_path.wide(), system_new_path.wide(), flags);
|
||||||
|
return (move_status != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool File::RenameLink(Namespace* namespc,
|
bool File::RenameLink(Namespace* namespc,
|
||||||
const char* old_path,
|
const char* old_path,
|
||||||
const char* new_path) {
|
const char* new_path) {
|
||||||
File::Type type = GetType(namespc, old_path, false);
|
File::Type type = GetType(namespc, old_path, false);
|
||||||
if (type == kIsLink) {
|
if (type != kIsLink) {
|
||||||
Utf8ToWideScope system_old_path(old_path);
|
|
||||||
Utf8ToWideScope system_new_path(new_path);
|
|
||||||
DWORD flags = MOVEFILE_WRITE_THROUGH | MOVEFILE_REPLACE_EXISTING;
|
|
||||||
// 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.
|
|
||||||
if (Directory::Exists(namespc, new_path) == Directory::EXISTS) {
|
|
||||||
bool result = true;
|
|
||||||
if (GetType(namespc, new_path, false) == kIsLink) {
|
|
||||||
result = DeleteLink(namespc, new_path);
|
|
||||||
} else {
|
|
||||||
result = Delete(namespc, new_path);
|
|
||||||
}
|
|
||||||
// Bail out if the Delete calls fail.
|
|
||||||
if (!result) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int move_status =
|
|
||||||
MoveFileExW(system_old_path.wide(), system_new_path.wide(), flags);
|
|
||||||
return (move_status != 0);
|
|
||||||
} else {
|
|
||||||
SetLastError(ERROR_FILE_NOT_FOUND);
|
SetLastError(ERROR_FILE_NOT_FOUND);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
Utf8ToWideScope system_old_path(old_path);
|
||||||
|
Utf8ToWideScope system_new_path(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)) {
|
||||||
|
// Bail out if the DeleteLink call fails.
|
||||||
|
if (!DeleteLink(namespc, new_path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int move_status =
|
||||||
|
MoveFileExW(system_old_path.wide(), system_new_path.wide(), flags);
|
||||||
|
return (move_status != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool File::Copy(Namespace* namespc,
|
bool File::Copy(Namespace* namespc,
|
||||||
const char* old_path,
|
const char* old_path,
|
||||||
const char* new_path) {
|
const char* new_path) {
|
||||||
File::Type type = GetType(namespc, old_path, false);
|
File::Type type = GetType(namespc, old_path, false);
|
||||||
if (type == kIsFile) {
|
if (type != kIsFile) {
|
||||||
Utf8ToWideScope system_old_path(old_path);
|
|
||||||
Utf8ToWideScope system_new_path(new_path);
|
|
||||||
bool success = CopyFileExW(system_old_path.wide(), system_new_path.wide(),
|
|
||||||
NULL, NULL, NULL, 0) != 0;
|
|
||||||
return success;
|
|
||||||
} else {
|
|
||||||
SetLastError(ERROR_FILE_NOT_FOUND);
|
SetLastError(ERROR_FILE_NOT_FOUND);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
Utf8ToWideScope system_old_path(old_path);
|
||||||
|
Utf8ToWideScope system_new_path(new_path);
|
||||||
|
bool success = CopyFileExW(system_old_path.wide(), system_new_path.wide(),
|
||||||
|
NULL, NULL, NULL, 0) != 0;
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t File::LengthFromPath(Namespace* namespc, const char* name) {
|
int64_t File::LengthFromPath(Namespace* namespc, const char* name) {
|
||||||
|
|
|
@ -50,11 +50,11 @@ abstract class Link implements FileSystemEntity {
|
||||||
* components are created. The directories in the path of [target] are
|
* components are created. The directories in the path of [target] are
|
||||||
* not affected, unless they are also in [path].
|
* not affected, unless they are also in [path].
|
||||||
*
|
*
|
||||||
* On the Windows platform, this will only work with directories, and the
|
* On the Windows platform, this call will create a true symbolic link
|
||||||
* target directory must exist. The link will be created as a Junction.
|
* instead of a Junction. In order to create a symbolic link on Windows, Dart
|
||||||
* Only absolute links will be created, and relative paths to the target
|
* must be run in Administrator mode or the system must have Developer Mode
|
||||||
* will be converted to absolute paths by joining them with the path of the
|
* enabled, otherwise a [FileSystemException] will be raised with
|
||||||
* directory the link is contained in.
|
* `ERROR_PRIVILEGE_NOT_HELD` set as the errno when this call is made.
|
||||||
*
|
*
|
||||||
* On other platforms, the posix symlink() call is used to make a symbolic
|
* On other platforms, the posix symlink() call is used to make a symbolic
|
||||||
* link containing the string [target]. If [target] is a relative path,
|
* link containing the string [target]. If [target] is a relative path,
|
||||||
|
@ -71,10 +71,11 @@ abstract class Link implements FileSystemEntity {
|
||||||
* non-existing path components are created. The directories in
|
* non-existing path components are created. The directories in
|
||||||
* the path of [target] are not affected, unless they are also in [path].
|
* the path of [target] are not affected, unless they are also in [path].
|
||||||
*
|
*
|
||||||
* On the Windows platform, this will only work with directories, and the
|
* On the Windows platform, this call will create a true symbolic link
|
||||||
* target directory must exist. The link will be created as a Junction.
|
* instead of a Junction. In order to create a symbolic link on Windows, Dart
|
||||||
* Only absolute links will be created, and relative paths to the target
|
* must be run in Administrator mode or the system must have Developer Mode
|
||||||
* will be converted to absolute paths.
|
* enabled, otherwise a [FileSystemException] will be raised with
|
||||||
|
* `ERROR_PRIVILEGE_NOT_HELD` set as the errno when this call is made.
|
||||||
*
|
*
|
||||||
* On other platforms, the posix symlink() call is used to make a symbolic
|
* On other platforms, the posix symlink() call is used to make a symbolic
|
||||||
* link containing the string [target]. If [target] is a relative path,
|
* link containing the string [target]. If [target] is a relative path,
|
||||||
|
@ -85,9 +86,6 @@ abstract class Link implements FileSystemEntity {
|
||||||
/**
|
/**
|
||||||
* Synchronously updates the link. Calling [updateSync] on a non-existing link
|
* Synchronously updates the link. Calling [updateSync] on a non-existing link
|
||||||
* will throw an exception.
|
* will throw an exception.
|
||||||
*
|
|
||||||
* On the Windows platform, this will only work with directories, and the
|
|
||||||
* target directory must exist.
|
|
||||||
*/
|
*/
|
||||||
void updateSync(String target);
|
void updateSync(String target);
|
||||||
|
|
||||||
|
@ -95,9 +93,6 @@ abstract class Link implements FileSystemEntity {
|
||||||
* Updates the link. Returns a [:Future<Link>:] that completes with the
|
* Updates the link. Returns a [:Future<Link>:] that completes with the
|
||||||
* link when it has been updated. Calling [update] on a non-existing link
|
* link when it has been updated. Calling [update] on a non-existing link
|
||||||
* will complete its returned future with an exception.
|
* will complete its returned future with an exception.
|
||||||
*
|
|
||||||
* On the Windows platform, this will only work with directories, and the
|
|
||||||
* target directory must exist.
|
|
||||||
*/
|
*/
|
||||||
Future<Link> update(String target);
|
Future<Link> update(String target);
|
||||||
|
|
||||||
|
@ -183,9 +178,6 @@ class _Link extends FileSystemEntity implements Link {
|
||||||
Link get absolute => new Link.fromRawPath(_rawAbsolutePath);
|
Link get absolute => new Link.fromRawPath(_rawAbsolutePath);
|
||||||
|
|
||||||
Future<Link> create(String target, {bool recursive: false}) {
|
Future<Link> create(String target, {bool recursive: false}) {
|
||||||
if (Platform.isWindows) {
|
|
||||||
target = _makeWindowsLinkTarget(target);
|
|
||||||
}
|
|
||||||
var result =
|
var result =
|
||||||
recursive ? parent.create(recursive: true) : new Future.value(null);
|
recursive ? parent.create(recursive: true) : new Future.value(null);
|
||||||
return result
|
return result
|
||||||
|
@ -204,28 +196,10 @@ class _Link extends FileSystemEntity implements Link {
|
||||||
if (recursive) {
|
if (recursive) {
|
||||||
parent.createSync(recursive: true);
|
parent.createSync(recursive: true);
|
||||||
}
|
}
|
||||||
if (Platform.isWindows) {
|
|
||||||
target = _makeWindowsLinkTarget(target);
|
|
||||||
}
|
|
||||||
var result = _File._createLink(_Namespace._namespace, _rawPath, target);
|
var result = _File._createLink(_Namespace._namespace, _rawPath, target);
|
||||||
throwIfError(result, "Cannot create link", path);
|
throwIfError(result, "Cannot create link", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put target into the form "\??\C:\my\target\dir".
|
|
||||||
String _makeWindowsLinkTarget(String target) {
|
|
||||||
Uri base = new Uri.file('${Directory.current.path}\\');
|
|
||||||
Uri link = new Uri.file(path);
|
|
||||||
Uri destination = new Uri.file(target);
|
|
||||||
String result = base.resolveUri(link).resolveUri(destination).toFilePath();
|
|
||||||
if (result.length > 3 && result[1] == ':' && result[2] == '\\') {
|
|
||||||
return '\\??\\$result';
|
|
||||||
} else {
|
|
||||||
throw new FileSystemException(
|
|
||||||
'Target $result of Link.create on Windows cannot be converted'
|
|
||||||
' to start with a drive letter. Unexpected error.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateSync(String target) {
|
void updateSync(String target) {
|
||||||
// TODO(12414): Replace with atomic update, where supported by platform.
|
// TODO(12414): Replace with atomic update, where supported by platform.
|
||||||
// Atomically changing a link can be done by creating the new link, with
|
// Atomically changing a link can be done by creating the new link, with
|
||||||
|
|
|
@ -7,7 +7,6 @@ import "package:expect/expect.dart";
|
||||||
import "package:path/path.dart";
|
import "package:path/path.dart";
|
||||||
import "dart:async";
|
import "dart:async";
|
||||||
import "dart:io";
|
import "dart:io";
|
||||||
import "dart:isolate";
|
|
||||||
|
|
||||||
// Test the dart:io Link class.
|
// Test the dart:io Link class.
|
||||||
|
|
||||||
|
@ -57,8 +56,8 @@ testCreateSync() {
|
||||||
|
|
||||||
// Test FileSystemEntity.identical on files, directories, and links,
|
// Test FileSystemEntity.identical on files, directories, and links,
|
||||||
// reached by different paths.
|
// reached by different paths.
|
||||||
Expect
|
Expect.isTrue(
|
||||||
.isTrue(FileSystemEntity.identicalSync(createdDirectly, createdDirectly));
|
FileSystemEntity.identicalSync(createdDirectly, createdDirectly));
|
||||||
Expect.isFalse(
|
Expect.isFalse(
|
||||||
FileSystemEntity.identicalSync(createdDirectly, createdThroughLink));
|
FileSystemEntity.identicalSync(createdDirectly, createdThroughLink));
|
||||||
Expect.isTrue(FileSystemEntity.identicalSync(
|
Expect.isTrue(FileSystemEntity.identicalSync(
|
||||||
|
@ -190,6 +189,38 @@ testRenameSync() {
|
||||||
Expect.isFalse(link2.existsSync());
|
Expect.isFalse(link2.existsSync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testRenameToLink(String base, String target) {
|
||||||
|
Link link1 = Link(join(base, '1'))..createSync(target);
|
||||||
|
Link link2 = Link(join(base, '2'))..createSync(target);
|
||||||
|
Expect.isTrue(link1.existsSync());
|
||||||
|
Expect.isTrue(link2.existsSync());
|
||||||
|
Link renamed = link1.renameSync(link2.path);
|
||||||
|
Expect.isFalse(link1.existsSync());
|
||||||
|
Expect.isTrue(renamed.existsSync());
|
||||||
|
renamed.deleteSync();
|
||||||
|
Expect.isFalse(renamed.existsSync());
|
||||||
|
}
|
||||||
|
|
||||||
|
testRenameToTarget(String linkName, String target, bool isDirectory) {
|
||||||
|
Link link = Link(linkName)..createSync(target);
|
||||||
|
Expect.isTrue(link.existsSync());
|
||||||
|
try {
|
||||||
|
Link renamed = link.renameSync(target);
|
||||||
|
if (isDirectory) {
|
||||||
|
Expect.fail('Renaming a link to the name of an existing directory ' +
|
||||||
|
'should fail');
|
||||||
|
}
|
||||||
|
Expect.isTrue(renamed.existsSync());
|
||||||
|
renamed.deleteSync();
|
||||||
|
} on FileSystemException catch (_) {
|
||||||
|
if (isDirectory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Expect.fail('Renaming a link to the name of an existing file should ' +
|
||||||
|
'not fail');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Directory baseDir = Directory.systemTemp.createTempSync('dart_link');
|
Directory baseDir = Directory.systemTemp.createTempSync('dart_link');
|
||||||
String base = baseDir.path;
|
String base = baseDir.path;
|
||||||
Directory dir = new Directory(join(base, 'a'))..createSync();
|
Directory dir = new Directory(join(base, 'a'))..createSync();
|
||||||
|
@ -198,6 +229,11 @@ testRenameSync() {
|
||||||
testRename(base, file.path);
|
testRename(base, file.path);
|
||||||
testRename(base, dir.path);
|
testRename(base, dir.path);
|
||||||
|
|
||||||
|
testRenameToLink(base, file.path);
|
||||||
|
|
||||||
|
testRenameToTarget(join(base, 'fileLink'), file.path, false);
|
||||||
|
testRenameToTarget(join(base, 'dirLink'), dir.path, true);
|
||||||
|
|
||||||
baseDir.deleteSync(recursive: true);
|
baseDir.deleteSync(recursive: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue