[io/doc/test] Modify the Windows symlink resolution behavior so that resolving a link that points to a non-existent file results in a type of `notFound`, which is consistent with all other platforms.
Bug:https://github.com/dart-lang/sdk/issues/53684
Change-Id: I1b594e1a85906d1f510358eec71792ea15ab801b
CoreLibraryReviewExempt: VM- and doc-only
Tested: unit tests
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/331841
Reviewed-by: Lasse Nielsen <lrn@google.com>
Commit-Queue: Brian Quinlan <bquinlan@google.com>
diff --git a/runtime/bin/file_win.cc b/runtime/bin/file_win.cc
index bbfc4fe..7ffd66a 100644
--- a/runtime/bin/file_win.cc
+++ b/runtime/bin/file_win.cc
@@ -1130,7 +1130,12 @@
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
if (target_handle == INVALID_HANDLE_VALUE) {
- result = File::kIsLink;
+ DWORD last_error = GetLastError();
+ if ((last_error == ERROR_FILE_NOT_FOUND) ||
+ (last_error == ERROR_PATH_NOT_FOUND)) {
+ return kDoesNotExist;
+ }
+ result = kIsLink;
} else {
BY_HANDLE_FILE_INFORMATION info;
if (!GetFileInformationByHandle(target_handle, &info)) {
@@ -1139,8 +1144,8 @@
}
CloseHandle(target_handle);
return ((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
- ? File::kIsDirectory
- : File::kIsFile;
+ ? kIsDirectory
+ : kIsFile;
}
} else {
result = kIsLink;
diff --git a/sdk/lib/io/file_system_entity.dart b/sdk/lib/io/file_system_entity.dart
index 07d176f..d9f16c7 100644
--- a/sdk/lib/io/file_system_entity.dart
+++ b/sdk/lib/io/file_system_entity.dart
@@ -308,6 +308,10 @@
/// ```
/// since `Uri.resolve` removes `..` segments. This will result in the Windows
/// behavior.
+ ///
+ /// On Windows, attempting to resolve a symbolic link where the link type
+ /// does not match the type of the resolved file system object will cause the
+ /// Future to complete with a [PathAccessException] error.
Future<String> resolveSymbolicLinks() {
return _File._dispatchWithNamespace(
_IOService.fileResolveSymbolicLinks, [null, _rawPath]).then((response) {
@@ -343,6 +347,11 @@
/// ```
/// since `Uri.resolve` removes `..` segments. This will result in the Windows
/// behavior.
+ ///
+ /// On Windows, a symbolic link is created as either a file link or a
+ /// directory link. Attempting to resolve such a symbolic link requires the
+ /// link type to match the type of the file system object that it points to,
+ /// otherwise it throws a [PathAccessException].
String resolveSymbolicLinksSync() {
var result = _resolveSymbolicLinks(_Namespace._namespace, _rawPath);
_throwIfError(result, "Cannot resolve symbolic links", path);
@@ -685,11 +694,15 @@
/// Synchronously finds the type of file system object that a path points to.
///
- /// Returns [FileSystemEntityType.link] only if [followLinks] is `false` and if
- /// [path] points to a link.
- ///
/// Returns [FileSystemEntityType.notFound] if [path] does not point to a file
/// system object or if any other error occurs in looking up the path.
+ ///
+ /// If [path] points to a link and [followLinks] is `true` then the result
+ /// will be for the file system object that the link points to. If that
+ /// object does not exist then the result will be
+ /// [FileSystemEntityType.notFound]. If [path] points to a link and
+ /// [followLinks] is `false` then the result will be
+ /// [FileSystemEntityType.link].
static FileSystemEntityType typeSync(String path, {bool followLinks = true}) {
return _getTypeSync(_toUtf8Array(path), followLinks);
}
diff --git a/sdk/lib/io/link.dart b/sdk/lib/io/link.dart
index 7ae352c..97f001c 100644
--- a/sdk/lib/io/link.dart
+++ b/sdk/lib/io/link.dart
@@ -48,10 +48,20 @@
/// not affected, unless they are also in [path].
///
/// On the Windows platform, this call will create a true symbolic link
- /// instead of a junction. The link represents a file or directory and
- /// does not change its type after creation. If [target] exists then
- /// the type of the link will match the type [target], otherwise a file
- /// symlink is created.
+ /// instead of a junction. Windows treats links to files and links to
+ /// directories as different and non-interchangable kinds of links.
+ /// Each link is either a file-link or a directory-link, and the type is
+ /// chosen when the link is created, and the link then counts as either a
+ /// file or directory for most purposes. Different Win32 API calls are
+ /// used to manipulate each. For example, the `DeleteFile` function is
+ /// used to delete links to files, and `RemoveDirectory` must be used to
+ /// delete links to directories.
+ ///
+ /// The created Windows symbolic link will match the type of the [target],
+ /// if [target] exists, otherwise a file-link is created. The type of the
+ /// created link will not change if [target] is later replaced by something
+ /// of a different type and the link will not be resolvable by
+ /// [resolveSymbolicLinks].
///
/// In order to create a symbolic link on Windows, Dart must be run in
/// Administrator mode or the system must have Developer Mode enabled,
@@ -76,10 +86,20 @@
/// the path of [target] are not affected, unless they are also in [path].
///
/// On the Windows platform, this call will create a true symbolic link
- /// instead of a junction. The link represents a file or directory and
- /// does not change its type after creation. If [target] exists then
- /// the type of the link will match the type [target], otherwise a file
- /// symlink is created.
+ /// instead of a junction. Windows treats links to files and links to
+ /// directories as different and non-interchangable kinds of links.
+ /// Each link is either a file-link or a directory-link, and the type is
+ /// chosen when the link is created, and the link then counts as either a
+ /// file or directory for most purposes. Different Win32 API calls are
+ /// used to manipulate each. For example, the `DeleteFile` function is
+ /// used to delete links to files, and `RemoveDirectory` must be used to
+ /// delete links to directories.
+ ///
+ /// The created Windows symbolic link will match the type of the [target],
+ /// if [target] exists, otherwise a file-link is created. The type of the
+ /// created link will not change if [target] is later replaced by something
+ /// of a different type and the link will not be resolvable by
+ /// [resolveSymbolicLinks].
///
/// In order to create a symbolic link on Windows, Dart must be run in
/// Administrator mode or the system must have Developer Mode enabled,
@@ -91,16 +111,22 @@
/// it will be interpreted relative to the directory containing the link.
void createSync(String target, {bool recursive = false});
- /// Synchronously updates the link.
+ /// Synchronously updates an existing link.
///
- /// Calling [updateSync] on a non-existing link will throw an exception.
+ /// Deletes the existing link at [path] and uses [createSync] to create a new
+ /// link to [target]. Throws [PathNotFoundException] if the original link
+ /// does not exist or with any [FileSystemException] that [deleteSync] or
+ /// [createSync] can throw.
void updateSync(String target);
- /// Updates the link.
+ /// Updates an existing link.
///
- /// Returns a `Future<Link>` that completes with the
- /// link when it has been updated. Calling [update] on a non-existing link
- /// will complete its returned future with an exception.
+ /// Deletes the existing link at [path] and creates a new link to [target],
+ /// using [create].
+ ///
+ /// Returns a future which completes with this `Link` if successful,
+ /// and with a [PathNotFoundException] if there is no existing link at [path],
+ /// or with any [FileSystemException] that [delete] or [create] can throw.
Future<Link> update(String target);
Future<String> resolveSymbolicLinks();
diff --git a/tests/standalone/io/link_async_test.dart b/tests/standalone/io/link_async_test.dart
index 0b33f61..e23e812 100644
--- a/tests/standalone/io/link_async_test.dart
+++ b/tests/standalone/io/link_async_test.dart
@@ -280,11 +280,24 @@
});
}
+Future testBrokenLinkTypeSync(_) async {
+ String base = (await Directory.systemTemp.createTemp('dart_link')).path;
+ String link = join(base, 'link');
+ await Link(link).create('does not exist');
+
+ Expect.equals(FileSystemEntityType.link,
+ await FileSystemEntity.type(link, followLinks: false));
+
+ Expect.equals(FileSystemEntityType.notFound,
+ await FileSystemEntity.type(link, followLinks: true));
+}
+
main() {
asyncStart();
testCreate()
.then(testCreateLoopingLink)
.then(testRename)
.then(testRelativeLinks)
+ .then(testBrokenLinkTypeSync)
.then((_) => asyncEnd());
}
diff --git a/tests/standalone/io/link_test.dart b/tests/standalone/io/link_test.dart
index 144c073..cb0700b 100644
--- a/tests/standalone/io/link_test.dart
+++ b/tests/standalone/io/link_test.dart
@@ -314,6 +314,18 @@
sandbox.deleteSync(recursive: true);
}
+testBrokenLinkTypeSync() {
+ String base = Directory.systemTemp.createTempSync('dart_link').path;
+ String link = join(base, 'link');
+ Link(link).createSync('does not exist');
+
+ Expect.equals(FileSystemEntityType.link,
+ FileSystemEntity.typeSync(link, followLinks: false));
+
+ Expect.equals(FileSystemEntityType.notFound,
+ FileSystemEntity.typeSync(link, followLinks: true));
+}
+
main() {
testCreateSync();
testCreateLoopingLink();
@@ -321,4 +333,5 @@
testLinkErrorSync();
testRelativeLinksSync();
testIsDir();
+ testBrokenLinkTypeSync();
}
diff --git a/tests/standalone/io/resolve_symbolic_links_test.dart b/tests/standalone/io/resolve_symbolic_links_test.dart
index 9ff0e9e..01df6ac 100644
--- a/tests/standalone/io/resolve_symbolic_links_test.dart
+++ b/tests/standalone/io/resolve_symbolic_links_test.dart
@@ -95,6 +95,8 @@
tempDir.delete(recursive: true);
});
}));
+
+ asyncTest(testLinkTargetTypeChangedAfterCreation);
}
Future makeEntities(String temp) {
@@ -154,3 +156,32 @@
.then((identical) => Expect.isTrue(identical));
});
}
+
+Future testLinkTargetTypeChangedAfterCreation() async {
+ // Test the following scenario:
+ // 1. create a file
+ // 2. create a link to that file
+ // 3. replace the file with a directory
+ // 4. attempt to resolve the link
+ final tmp =
+ await Directory.systemTemp.createTemp('dart_resolve_symbolic_links');
+ final tmpPath = tmp.absolute.path;
+ final filePath = join(tmpPath, "file");
+ final linkPath = join(tmpPath, "link");
+ await File(filePath).create();
+ await Link(linkPath).create(filePath);
+
+ Expect.isTrue(FileSystemEntity.identicalSync(
+ filePath, await Directory(linkPath).resolveSymbolicLinks()));
+
+ await File(filePath).delete();
+ await Directory(filePath).create();
+
+ if (Platform.isWindows) {
+ await asyncExpectThrows<PathAccessException>(
+ Directory(linkPath).resolveSymbolicLinks());
+ } else {
+ Expect.isTrue(await FileSystemEntity.identical(
+ filePath, await Directory(linkPath).resolveSymbolicLinks()));
+ }
+}
diff --git a/tests/standalone/io/windows_file_system_async_links_test.dart b/tests/standalone/io/windows_file_system_async_links_test.dart
index 207d9a5..4ab13c2 100644
--- a/tests/standalone/io/windows_file_system_async_links_test.dart
+++ b/tests/standalone/io/windows_file_system_async_links_test.dart
@@ -57,7 +57,7 @@
.then((_) => FutureExpect.isFalse(FileSystemEntity.isDirectory(y)))
.then((_) => FutureExpect.isFalse(FileSystemEntity.isDirectory(x)))
.then((_) => FutureExpect.equals(
- FileSystemEntityType.link, FileSystemEntity.type(y)))
+ FileSystemEntityType.notFound, FileSystemEntity.type(y)))
.then((_) => FutureExpect.equals(
FileSystemEntityType.notFound, FileSystemEntity.type(x)))
.then((_) => FutureExpect.equals(FileSystemEntityType.link,
@@ -86,8 +86,8 @@
FileSystemEntityType.notFound, FileSystemEntity.type(y)))
.then((_) => FutureExpect.equals(FileSystemEntityType.notFound,
FileSystemEntity.type(y, followLinks: false)))
- .then((_) =>
- FutureExpect.equals(FileSystemEntityType.directory, FileSystemEntity.type(x)))
+ .then(
+ (_) => FutureExpect.equals(FileSystemEntityType.directory, FileSystemEntity.type(x)))
.then((_) => FutureExpect.equals(FileSystemEntityType.directory, FileSystemEntity.type(x, followLinks: false)))
.then((_) => FutureExpect.throws(new Link(y).target()))
.then((_) => temp.delete(recursive: true));
diff --git a/tests/standalone/io/windows_file_system_links_test.dart b/tests/standalone/io/windows_file_system_links_test.dart
index dcbc571c..6160e37 100644
--- a/tests/standalone/io/windows_file_system_links_test.dart
+++ b/tests/standalone/io/windows_file_system_links_test.dart
@@ -36,7 +36,7 @@
Expect.isFalse(FileSystemEntity.isLinkSync(x));
Expect.isFalse(FileSystemEntity.isDirectorySync(y));
Expect.isFalse(FileSystemEntity.isDirectorySync(x));
- Expect.equals(FileSystemEntityType.link, FileSystemEntity.typeSync(y));
+ Expect.equals(FileSystemEntityType.notFound, FileSystemEntity.typeSync(y));
Expect.equals(FileSystemEntityType.notFound, FileSystemEntity.typeSync(x));
Expect.equals(FileSystemEntityType.link,
FileSystemEntity.typeSync(y, followLinks: false));