// Copyright (c) 2013, 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. part of "dart:io"; /// References to filesystem links. @pragma("vm:entry-point") abstract interface class Link implements FileSystemEntity { /// Creates a Link object. @pragma("vm:entry-point") factory Link(String path) { final IOOverrides? overrides = IOOverrides.current; if (overrides == null) { return new _Link(path); } return overrides.createLink(path); } @pragma("vm:entry-point") factory Link.fromRawPath(Uint8List rawPath) { // TODO(bkonyi): handle overrides return new _Link.fromRawPath(rawPath); } /// Creates a [Link] object. /// /// If [path] is a relative path, it will be interpreted relative to the /// current working directory (see [Directory.current]), when used. /// /// If [path] is an absolute path, it will be immune to changes to the /// current working directory. factory Link.fromUri(Uri uri) => new Link(uri.toFilePath()); /// Creates a symbolic link in the file system. /// /// The created link will point to the path at [target], whether that path /// exists or not. /// /// Returns a `Future` that completes with /// the link when it has been created. If the link path already exists, /// the future will complete with an error. /// /// If [recursive] is `false`, the default, the link is created /// only if all directories in its path exist. /// If [recursive] is `true`, all non-existing parent paths /// are created first. The directories in 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. 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, but then 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, /// 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 /// link containing the string [target]. If [target] is a relative path, /// it will be interpreted relative to the directory containing the link. Future create(String target, {bool recursive = false}); /// Creates a symbolic link in the file system. /// /// The created link will point to the path at [target], whether that path /// exists or not. /// /// If the link path already exists, an exception will be thrown. /// /// If [recursive] is `false`, the default, the link is created only if all /// directories in its path exist. If [recursive] is `true`, all /// non-existing parent paths are created first. The directories in /// 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. 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, but then 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, /// 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 /// link containing the string [target]. If [target] is a relative path, /// it will be interpreted relative to the directory containing the link. void createSync(String target, {bool recursive = false}); /// Synchronously updates an existing link. /// /// 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 any [FileSystemException] that [deleteSync] or /// [createSync] can throw. void updateSync(String target); /// Updates an existing link. /// /// 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 update(String target); Future resolveSymbolicLinks(); String resolveSymbolicLinksSync(); /// Renames this link. /// /// Returns a `Future` that completes with a [Link] /// for the renamed link. /// /// If [newPath] identifies an existing file or link, that entity is removed /// first. If [newPath] identifies an existing directory then the future /// completes with a [FileSystemException]. Future rename(String newPath); /// Synchronously renames this link. /// /// Returns a [Link] instance for the renamed link. /// /// If [newPath] identifies an existing file or link, that entity is removed /// first. If [newPath] identifies an existing directory then /// [FileSystemException] is thrown. Link renameSync(String newPath); /// Deletes this [Link]. /// /// If [recursive] is `false`: /// /// * If [path] corresponds to a link then that path is deleted. Otherwise, /// [delete] completes with a [FileSystemException]. /// /// If [recursive] is `true`: /// /// * The [FileSystemEntity] at [path] is deleted regardless of type. If /// [path] corresponds to a file or link, then that file or link is /// deleted. If [path] corresponds to a directory, then it and all /// sub-directories and files in those directories are deleted. Links /// are not followed when deleting recursively. Only the link is deleted, /// not its target. This behavior allows [delete] to be used to /// unconditionally delete any file system object. /// /// If this [Link] cannot be deleted, then [delete] completes with a /// [FileSystemException]. Future delete({bool recursive = false}); /// Synchronously deletes this [Link]. /// /// If [recursive] is `false`: /// /// * If [path] corresponds to a link then that path is deleted. Otherwise, /// [delete] throws a [FileSystemException]. /// /// If [recursive] is `true`: /// /// * The [FileSystemEntity] at [path] is deleted regardless of type. If /// [path] corresponds to a file or link, then that file or link is /// deleted. If [path] corresponds to a directory, then it and all /// sub-directories and files in those directories are deleted. Links /// are not followed when deleting recursively. Only the link is deleted, /// not its target. This behavior allows [delete] to be used to /// unconditionally delete any file system object. /// /// If this [Link] cannot be deleted, then [delete] throws a /// [FileSystemException]. void deleteSync({bool recursive = false}); /// A [Link] instance whose path is the absolute path to this [Link]. /// /// The absolute path is computed by prefixing /// a relative path with the current working directory, or returning /// an absolute path unchanged. Link get absolute; /// Gets the target of the link. /// /// Returns a future that completes with the path to the target. /// /// If the returned target is a relative path, it is relative to the /// directory containing the link. /// /// If the link does not exist, or is not a link, the future completes with /// a [FileSystemException]. Future target(); /// Synchronously gets the target of the link. /// /// Returns the path to the target. /// /// If the returned target is a relative path, it is relative to the /// directory containing the link. /// /// If the link does not exist, or is not a link, /// throws a [FileSystemException]. String targetSync(); } class _Link extends FileSystemEntity implements Link { final String _path; final Uint8List _rawPath; _Link(String path) : _path = path, _rawPath = FileSystemEntity._toUtf8Array(path); _Link.fromRawPath(Uint8List rawPath) : _rawPath = FileSystemEntity._toNullTerminatedUtf8Array(rawPath), _path = FileSystemEntity._toStringFromUtf8Array(rawPath); String get path => _path; String toString() => "Link: '$path'"; Future exists() => FileSystemEntity._isLinkRaw(_rawPath); bool existsSync() => FileSystemEntity._isLinkRawSync(_rawPath); Link get absolute => isAbsolute ? this : _Link(_absolutePath); Future create(String target, {bool recursive = false}) { var result = recursive ? parent.create(recursive: true) : new Future.value(null); return result .then( (_) => _File._dispatchWithNamespace(_IOService.fileCreateLink, [ null, _rawPath, target, ]), ) .then((response) { _checkForErrorResponse( response, "Cannot create link to target '$target'", path, ); return this; }); } void createSync(String target, {bool recursive = false}) { if (recursive) { parent.createSync(recursive: true); } var result = _File._createLink(_Namespace._namespace, _rawPath, target); throwIfError(result, "Cannot create link", path); } void updateSync(String target) { // TODO(12414): Replace with atomic update, where supported by platform. // Atomically changing a link can be done by creating the new link, with // a different name, and using the rename() posix call to move it to // the old name atomically. deleteSync(); createSync(target); } Future update(String target) { // TODO(12414): Replace with atomic update, where supported by platform. // Atomically changing a link can be done by creating the new link, with // a different name, and using the rename() posix call to move it to // the old name atomically. return delete().then((_) => create(target)); } Future _delete({bool recursive = false}) { if (recursive) { return new Directory.fromRawPath( _rawPath, ).delete(recursive: true).then((_) => this); } return _File._dispatchWithNamespace(_IOService.fileDeleteLink, [ null, _rawPath, ]).then((response) { _checkForErrorResponse(response, "Cannot delete link", path); return this; }); } void _deleteSync({bool recursive = false}) { if (recursive) { return new Directory.fromRawPath(_rawPath).deleteSync(recursive: true); } var result = _File._deleteLinkNative(_Namespace._namespace, _rawPath); throwIfError(result, "Cannot delete link", path); } Future rename(String newPath) { return _File._dispatchWithNamespace(_IOService.fileRenameLink, [ null, _rawPath, newPath, ]).then((response) { _checkForErrorResponse( response, "Cannot rename link to '$newPath'", path, ); return new Link(newPath); }); } Link renameSync(String newPath) { var result = _File._renameLink(_Namespace._namespace, _rawPath, newPath); throwIfError(result, "Cannot rename link '$path' to '$newPath'"); return new Link(newPath); } Future target() { return _File._dispatchWithNamespace(_IOService.fileLinkTarget, [ null, _rawPath, ]).then((response) { _checkForErrorResponse(response, "Cannot get target of link", path); return response as String; }); } String targetSync() { var result = _File._linkTarget(_Namespace._namespace, _rawPath); throwIfError(result, "Cannot read link", path); return result; } static throwIfError(Object? result, String msg, [String path = ""]) { if (result is OSError) { throw FileSystemException._fromOSError(result, msg, path); } } }