FileInfo/DirectoryInfo: enable transparent handling of symbolic links #52908
Description
Background and Motivation
Users may not be aware that the fileName
/path
they are providing is a symbolic link.
This proposal makes it possible to operate on such path without the user having to manually handle symbolic links using the APIs added in #24271.
Proposed API
A new constructor on FileInfo
allows to specify whether the instance should return information about the target instead of the symbolic link.
class FileInfo
{
public FileInfo(string fileName, bool followLinks) { }
}
class DirectoryInfo
{
public DirectoryInfo(string path, bool followLinks) { }
}
The argument causes information for the link target to be returned for the following properties. When there is no target they throw no such file.
class FileInfo
{
public bool IsReadOnly { get { throw null; } set { } }
public long Length { get { throw null; } }
}
class FileSystemInfo
{
public FileAttributes Attributes { get { throw null; } set { } }
public DateTime CreationTime { get { throw null; } set { } }
public DateTime CreationTimeUtc { get { throw null; } set { } }
public abstract bool Exists { get; }
public DateTime LastAccessTime { get { throw null; } set { } }
public DateTime LastAccessTimeUtc { get { throw null; } set { } }
public DateTime LastWriteTime { get { throw null; } set { } }
public DateTime LastWriteTimeUtc { get { throw null; } set { } }
// from https://github.com/dotnet/runtime/issues/24271
// always returns `false` when `followLinks` is `true`.
public bool IsSymbolicLink { get; }
}
It does not affect the following operations. Which are either:
- always working on the target, or
- working on the 'fileName'/'path' argument.
class DirectoryInfo
{
// applies to 'path':
public DirectoryInfo? Parent { get { throw null; } }
public DirectoryInfo Root { get { throw null; } }
// applies to 'target':
public void Create() { }
public DirectoryInfo CreateSubdirectory(string path) { throw null; }
public IEnumerable<DirectoryInfo> EnumerateDirectories() { throw null; }
public IEnumerable<DirectoryInfo> EnumerateDirectories(string searchPattern) { throw null; }
public IEnumerable<DirectoryInfo> EnumerateDirectories(string searchPattern, EnumerationOptions enumerationOptions) { throw null; }
public IEnumerable<DirectoryInfo> EnumerateDirectories(string searchPattern, SearchOption searchOption) { throw null; }
public IEnumerable<FileInfo> EnumerateFiles() { throw null; }
public IEnumerable<FileInfo> EnumerateFiles(string searchPattern) { throw null; }
public IEnumerable<FileInfo> EnumerateFiles(string searchPattern, EnumerationOptions enumerationOptions) { throw null; }
public IEnumerable<FileInfo> EnumerateFiles(string searchPattern, SearchOption searchOption) { throw null; }
public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos() { throw null; }
public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos(string searchPattern) { throw null; }
public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos(string searchPattern, EnumerationOptions enumerationOptions) { throw null; }
public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos(string searchPattern, SearchOption searchOption) { throw null; }
public DirectoryInfo[] GetDirectories() { throw null; }
public DirectoryInfo[] GetDirectories(string searchPattern) { throw null; }
public DirectoryInfo[] GetDirectories(string searchPattern, EnumerationOptions enumerationOptions) { throw null; }
public DirectoryInfo[] GetDirectories(string searchPattern, SearchOption searchOption) { throw null; }
public FileInfo[] GetFiles() { throw null; }
public FileInfo[] GetFiles(string searchPattern) { throw null; }
public FileInfo[] GetFiles(string searchPattern, EnumerationOptions enumerationOptions) { throw null; }
public FileInfo[] GetFiles(string searchPattern, SearchOption searchOption) { throw null; }
public FileSystemInfo[] GetFileSystemInfos() { throw null; }
public FileSystemInfo[] GetFileSystemInfos(string searchPattern) { throw null; }
public FileSystemInfo[] GetFileSystemInfos(string searchPattern, EnumerationOptions enumerationOptions) { throw null; }
public FileSystemInfo[] GetFileSystemInfos(string searchPattern, SearchOption searchOption) { throw null; }
// applies to 'path':
public void Delete(bool recursive) { }
public override void Delete() { }
public void MoveTo(string destDirName) { }
}
class FileInfo
{
// applies to 'fileName'
public DirectoryInfo? Directory { get { throw null; } }
public string? DirectoryName { get { throw null; } }
// applies to 'target':
public StreamWriter AppendText() { throw null; }
public FileInfo CopyTo(string destFileName) { throw null; }
public FileInfo CopyTo(string destFileName, bool overwrite) { throw null; }
public FileStream Create() { throw null; }
public StreamWriter CreateText() { throw null; }
public void Decrypt() { }
public void Encrypt() { }
public FileStream Open(FileMode mode) { throw null; }
public FileStream Open(FileMode mode, FileAccess access) { throw null; }
public FileStream Open(FileMode mode, FileAccess access, FileShare share) { throw null; }
public FileStream OpenRead() { throw null; }
public StreamReader OpenText() { throw null; }
public FileStream OpenWrite() { throw null; }
public FileInfo Replace(string destinationFileName, string? destinationBackupFileName) { throw null; }
public FileInfo Replace(string destinationFileName, string? destinationBackupFileName, bool ignoreMetadataErrors) { throw null; }
// applies to 'path':
public override void Delete() { }
public void MoveTo(string destFileName) { }
public void MoveTo(string destFileName, bool overwrite) { }
}
class FileSystemInfo
{
// applies to 'fileName'/'path':
protected string FullPath;
protected string OriginalPath;
public string Extension { get { throw null; } }
public virtual string FullName { get { throw null; } }
public abstract string Name { get; }
}
Usage Examples
FileInfo file = new FileInfo("/tmp/my-file", followLinks: true);
DateTime lastWriteTime = file.LastWriteTimeUtc;
while (true)
{
Thread.Sleep(1000);
file.Refresh();
DateTime writeTime = file.LastWriteTimeUtc;
if (writeTime != lastWriteTime)
{
Console.WriteLine("The file has changed");
lastWriteTime = writeTime;
}
}
Implementation
On Linux, this means the information for the properties is retrieved using stat
instead of vstat
.
For Delete
, the final target is first located, e.g. by calling realpath
, and that is then deleted.
edits:
- added
IsSymbolicLink
from Proposed API for symbolic links #24271 - renamed
followLink
tofollowLinks
. - updated proposal to make
Delete
target based. - added implementation section which covers Linux
- revert
Delete
to be path based again. - place
MoveTo
under path-based