mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 09:43:57 +00:00
[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>
This commit is contained in:
parent
10a5df43af
commit
097c84f11b
|
@ -1130,7 +1130,12 @@ File::Type File::GetType(Namespace* namespc,
|
|||
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 @@ File::Type File::GetType(Namespace* namespc,
|
|||
}
|
||||
CloseHandle(target_handle);
|
||||
return ((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
|
||||
? File::kIsDirectory
|
||||
: File::kIsFile;
|
||||
? kIsDirectory
|
||||
: kIsFile;
|
||||
}
|
||||
} else {
|
||||
result = kIsLink;
|
||||
|
|
|
@ -308,6 +308,10 @@ abstract class FileSystemEntity {
|
|||
/// ```
|
||||
/// 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 @@ abstract class FileSystemEntity {
|
|||
/// ```
|
||||
/// 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 @@ abstract class FileSystemEntity {
|
|||
|
||||
/// 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);
|
||||
}
|
||||
|
|
|
@ -48,10 +48,20 @@ abstract interface class Link implements FileSystemEntity {
|
|||
/// 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 @@ abstract interface class Link implements FileSystemEntity {
|
|||
/// 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 @@ abstract interface class Link implements FileSystemEntity {
|
|||
/// 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();
|
||||
|
|
|
@ -280,11 +280,24 @@ Future testRelativeLinks(_) {
|
|||
});
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
|
|
@ -314,6 +314,18 @@ testIsDir() async {
|
|||
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 @@ main() {
|
|||
testLinkErrorSync();
|
||||
testRelativeLinksSync();
|
||||
testIsDir();
|
||||
testBrokenLinkTypeSync();
|
||||
}
|
||||
|
|
|
@ -95,6 +95,8 @@ main() {
|
|||
tempDir.delete(recursive: true);
|
||||
});
|
||||
}));
|
||||
|
||||
asyncTest(testLinkTargetTypeChangedAfterCreation);
|
||||
}
|
||||
|
||||
Future makeEntities(String temp) {
|
||||
|
@ -154,3 +156,32 @@ Future testLink(String name) {
|
|||
.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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ Future testJunctionTypeDelete() {
|
|||
.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 @@ Future testJunctionTypeDelete() {
|
|||
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));
|
||||
|
|
|
@ -36,7 +36,7 @@ testJunctionTypeDelete() {
|
|||
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));
|
||||
|
|
Loading…
Reference in a new issue