Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
b63c7b7
ref declarations and src empty definitions
carlossanlop Jun 1, 2021
b223304
Implementation for Unix symlinks APIs and cross-platform unit tests.
carlossanlop Jun 1, 2021
2e822fd
Add windows implementation of [sym]link APIs
jozkee Jun 11, 2021
32e196c
Use ValueStringBuilder in ResolveLinkTarget
carlossanlop Jun 15, 2021
8ed2772
Build failure in FileSystemWatcher csproj due to missing new interop …
carlossanlop Jun 17, 2021
abbecf0
Fix build failure. Address documentation suggestions.
Jun 17, 2021
fc49855
Add missing csproj references to System.Net.Ping.Functional.Tests (pr…
carlossanlop Jun 18, 2021
86cbd19
Move CanCreateSymbolicLinks from FileSystemWatcher one class above, s…
Jun 18, 2021
480d3df
Fix some Microsoft.IO.Redist CI failures.
carlossanlop Jun 18, 2021
a6cd9da
Fix CI build failures from Net5Compat.Tests
carlossanlop Jun 18, 2021
6952eb7
Clean CanCreateSymbolicLinks for readability. Remove incomplete code.
carlossanlop Jun 21, 2021
797ace5
Support long paths in GetFinalPathNameByHandle
jozkee Jun 18, 2021
8639e3b
Use MS.IO.Redist friendly APIs for marshalling
jozkee Jun 19, 2021
6bb1aa9
Add IsBrowser check to CanCreateSymbolicLinks
carlossanlop Jun 21, 2021
88bcadb
Add more tests and fix some issues in windows
jozkee Jun 22, 2021
8701118
Add path to IO_InconsistentLinkType exception msg
carlossanlop Jun 22, 2021
d10e00c
Fix failure in test creating inconsistent file/dir type.
carlossanlop Jun 22, 2021
fa5ff40
Fix net48 error
jozkee Jun 22, 2021
6788db7
Address CreateSymbolicLink initial check on Unix to verify pathToTarg…
Jun 22, 2021
45392b4
Use RemoteExecutor for a test related to change the current working dir
jozkee Jun 23, 2021
38b6e88
Add missing argument to exception message in MS.IO.Redist
Jun 23, 2021
a70cdef
nit: Clean comment on CreateSymbolicLink p/invoke.
Jun 23, 2021
cbcf7af
Fix CI bug in Windows Nano where %TEMP% points to "C:\Windows\TEMP" b…
carlossanlop Jun 26, 2021
c43664a
Use ALLOW_UNPRIVILEGED_CREATE only on windows versions >= 10.0.14972
jozkee Jun 26, 2021
9460264
Do not use IsWindowsVersionAtLeast since isn't avail in ns2.0
jozkee Jun 28, 2021
bbaa35a
Use PrintName (Dos) instead of SubstituteName (NT)
jozkee Jun 28, 2021
7e2eaa9
Fix issues related to server share paths
jozkee Jun 29, 2021
0d4250b
Fix CI issues
jozkee Jun 29, 2021
1a05a56
Address suggestions
jozkee Jun 29, 2021
d656aed
Address suggestions
jozkee Jun 29, 2021
adcca16
Address suggestions about using PathInternal.IsExtended
jozkee Jun 29, 2021
35165b0
Add more scenarios for "file system entry type is inconsistent with t…
jozkee Jul 1, 2021
9d2694b
Remove duplicated validation checks
jozkee Jul 1, 2021
c79df26
Address suggestions for Unix
jozkee Jul 1, 2021
fc4241d
ifdef the list of paths used for theories
jozkee Jul 1, 2021
4881f2b
Add tests to verify the limit of followed links
jozkee Jul 2, 2021
b377cb0
Fix bug related to follow links limit in Unix
jozkee Jul 2, 2021
496487f
Trim extended prefix when the passed-in path is not extended
jozkee Jul 2, 2021
beaf701
Remove helper AssertFullNameEquals since is no longer needed and fix …
jozkee Jul 2, 2021
a5986fb
Add a check for versions < win10 build 14972 to fix silent error
jozkee Jul 2, 2021
b7ec269
Add Windows limit for ResolveLinkTarget to remarks
jozkee Jul 2, 2021
7f37a86
Remove pipe test
jozkee Jul 2, 2021
3e36f72
Remove lstat from ResolveLinkTarget
jozkee Jul 6, 2021
6f5b99a
Avoid testing the reparse point/soft link limit very precisely
jozkee Jul 8, 2021
2d09f7e
Fix LinkTarget invalidation logic
jozkee Jul 8, 2021
4b5e133
Make returnFinalTarget no longer optional
jozkee Jul 8, 2021
82dc88a
Merge branch 'main' of https://github.com/dotnet/runtime into symlinks
jozkee Jul 8, 2021
48e1af1
Try to fix failing tests found in CI
jozkee Jul 8, 2021
0495f59
Try smaller chains of links
jozkee Jul 8, 2021
40cb737
Avoid using GetTestFilePath in order to correctly compare against the…
jozkee Jul 9, 2021
995ead5
Use chain length of 20 instead of 30, which fails in Win 1809
jozkee Jul 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
ref declarations and src empty definitions
  • Loading branch information
carlossanlop authored and jozkee committed Jul 2, 2021
commit b63c7b7bde76dd5abd245a083fe83a8bd521a57b
22 changes: 22 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/IO/Directory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ public static DirectoryInfo CreateDirectory(string path)
return new DirectoryInfo(path, fullPath, isNormalized: true);
}

/// <summary>
/// Creates a directory symbolic link identified by <paramref name="path"/> that points to <paramref name="pathToTarget"/>.
/// </summary>
/// <param name="path">The location of the directory symbolic link.</param>
/// <param name="pathToTarget">The target of the directory symbolic link.</param>
/// <returns>A <see cref="DirectoryInfo"/> instance that wraps the newly created directory symbolic link.</returns>
public static FileSystemInfo CreateSymbolicLink(string path, string pathToTarget)
{
return new DirectoryInfo("");
}

// Tests if the given path refers to an existing DirectoryInfo on disk.
public static bool Exists([NotNullWhen(true)] string? path)
{
Expand Down Expand Up @@ -315,5 +326,16 @@ public static string[] GetLogicalDrives()
{
return FileSystem.GetLogicalDrives();
}

/// <summary>
/// Gets the target of the specified directory symbolic link.
/// </summary>
/// <param name="linkPath">The path of the directory symbolic link.</param>
/// <param name="returnFinalTarget"><see langword="true"/> to follow links to the final target; <see langword="false"/> to return the immediate next link.</param>
/// <returns>A <see cref="DirectoryInfo"/> instance if the symbolic link exists, independently if the target exists or not. <see langword="null"/> if the symbolic link does not exist.</returns>
public static System.IO.FileSystemInfo? ResolveLinkTarget(string linkPath, bool returnFinalTarget = false)
{
return null;
}
}
}
22 changes: 22 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/IO/File.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,17 @@ public static FileStream Create(string path, int bufferSize)
public static FileStream Create(string path, int bufferSize, FileOptions options)
=> new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None, bufferSize, options);

/// <summary>
/// Creates a file symbolic link identified by <paramref name="path"/> that points to <paramref name="pathToTarget"/>.
/// </summary>
/// <param name="path">The location of the file symbolic link.</param>
/// <param name="pathToTarget">The target of the file symbolic link.</param>
/// <returns>A <see cref="FileInfo"/> instance that wraps the newly created file symbolic link.</returns>
public static FileSystemInfo CreateSymbolicLink(string path, string pathToTarget)
{
return new FileInfo("");
}

// Deletes a file. The file specified by the designated path is deleted.
// If the file does not exist, Delete succeeds without throwing
// an exception.
Expand Down Expand Up @@ -1007,5 +1018,16 @@ private static async Task InternalWriteAllTextAsync(StreamWriter sw, string cont
? Task.FromCanceled(cancellationToken)
: InternalWriteAllLinesAsync(AsyncStreamWriter(path, encoding, append: true), contents, cancellationToken);
}

/// <summary>
/// Gets the target of the specified file symbolic link.
/// </summary>
/// <param name="linkPath">The path of the file symbolic link.</param>
/// <param name="returnFinalTarget"><see langword="true"/> to follow links to the final target; <see langword="false"/> to return the immediate next link.</param>
/// <returns>A <see cref="FileInfo"/> instance if the symbolic link exists, independently if the target exists or not. <see langword="null"/> if the symbolic link does not exist.</returns>
public static System.IO.FileSystemInfo? ResolveLinkTarget(string linkPath, bool returnFinalTarget = false)
{
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,25 @@ public DateTime LastWriteTimeUtc
set => LastWriteTimeCore = File.GetUtcDateTimeOffset(value);
}

/// <summary>
/// If this <see cref="FileSystemInfo"/> instance represents a link, returns the link target's path.
/// If the link does not exist, returns <see langword="null"/>.
/// </summary>
public string? LinkTarget { get { return null; } }

Copy link
Member

@tmds tmds Jul 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In #24271 there was general consensus an IsLink property makes sense, though it was deferred to later.

Thinking out-loud: if the property exists, would it be taken into account by this method?

e.g. when !IsLink throw InvalidOperationException or return null immediately?

Adding this later would be a breaking change, and won't happen.

This was deferred because the semantics of IsLink are ambiguous. Maybe as part of this implementation it became more clear, and we can return false for anything where LinkTarget is expected to return null?

cc @mklement0 @adamsitnik

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, @tmds; #53577 is the follow-up issue about IsLink; to quote my comment from #53577 (comment):

We need to get clarity on what .IsLink returning true is meant to signal:

  • (a) Does it indicate whether the file-system itself considers something a link, as signaled on NTFS via the name-surrogate bit in the reparse-tag value?

  • (b) Or does it indicate that .NET not only knows that it is a link, but also knows how to explicitly resolve the link to its target? That is, can members such as .LinkTarget or .ResolveLinkTarget() predictably be called?

And later my thought was:

I'm now leaning toward (a):

  • Let .IsLink report everything that the file system thinks is a link, even if .NET doesn't understand the particular NTFS link type and cannot query its target,
  • combined with with making .LinkTarget and .ResolveLinkTarget() simply return null for unknown link types; again, this NTFS-specific scenario could then be handled via the reparse-point API (assuming the caller understands the link format).

Only if (b) is implemented can the property be used to short-circuit.

And based on the implementation here it sounds non-links / unknown link types should return null rather than throw.

But ultimately I'm not sure whether (a) or (b) is the way to go (and perhaps the problem will rarely, if ever, arise).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the property exists, would it be taken into account by this method?
e.g. when !IsLink throw InvalidOperationException or return null immediately?

I think IsLink will need to be consistent with LinkTarget i.e: IsLink == (LinkTarget != null) if we apply your other suggestion #54253 (comment) (do not query LinkTarget multiple times even if null was returned), then I think that would be enough to achieve consistency.

After that, adding IsLink in another release should no longer be a breaking change. Does that sounds OK to you folks?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really there is two common scenario: (1) manage link entity itself, (2) resolving link implicitly or explicitly in a code. Each of these scenarios requires a different approach. They are mutually exclusive. The first scenario requires a separate API like public class SymbolicLink or public class MountPoint. Second scenario requires injection in existing API/behavior that we do in the PR.
Seeing how slowly this PR is progressing, I would put it off until the next version altogether. Much more important is the behavior of the existing API. For example, we already account for reparse points (RPs) in Directory.Remove. We need to implement the same in enumeration API. Then maybe we can create some internal API for RPs. This would be a great basis for further research. Looking specifically at behavior, I believe we probably won't need a generic API for links at all.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iSazonov I'm also leaning towards a.

When IsLink returns false I'd expect LinkTarget to throw InvalidOperationException. or return null without querying the target.

In any case, when IsLink returns true, LinkTarget can always still return null because the underlying file may have changed and no longer be a link. The user must handle this case.

/// <summary>
/// Creates a symbolic link located in <see cref="FullName"/> that points to the specified <paramref name="pathToTarget"/>.
/// </summary>
/// <param name="pathToTarget">The path of the symbolic link target.</param>
public void CreateAsSymbolicLink(string pathToTarget) { }

/// <summary>
/// Gets the target of the specified symbolic link.
/// </summary>
/// <param name="returnFinalTarget"><see langword="true"/> to follow links to the final target; <see langword="false"/> to return the immediate next link.</param>
/// <returns>A <see cref="FileInfo"/> instance if the symbolic link exists, independently if the target exists or not. <see langword="null"/> if the symbolic link does not exist.</returns>
public System.IO.FileSystemInfo? ResolveLinkTarget(bool returnFinalTarget = false) { return null; }

/// <summary>
/// Returns the original path. Use FullName or Name properties for the full path or file/directory name.
/// </summary>
Expand Down
7 changes: 7 additions & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7412,6 +7412,7 @@ public override void WriteByte(byte value) { }
public static partial class Directory
{
public static System.IO.DirectoryInfo CreateDirectory(string path) { throw null; }
public static System.IO.FileSystemInfo CreateSymbolicLink(string path, string pathToTarget) { throw null; }
public static void Delete(string path) { }
public static void Delete(string path, bool recursive) { }
public static System.Collections.Generic.IEnumerable<string> EnumerateDirectories(string path) { throw null; }
Expand Down Expand Up @@ -7450,6 +7451,7 @@ public static void Delete(string path, bool recursive) { }
public static string[] GetLogicalDrives() { throw null; }
public static System.IO.DirectoryInfo? GetParent(string path) { throw null; }
public static void Move(string sourceDirName, string destDirName) { }
public static System.IO.FileSystemInfo? ResolveLinkTarget(string linkPath, bool returnFinalTarget = false) { throw null; }
public static void SetCreationTime(string path, System.DateTime creationTime) { }
public static void SetCreationTimeUtc(string path, System.DateTime creationTimeUtc) { }
public static void SetCurrentDirectory(string path) { }
Expand All @@ -7474,10 +7476,13 @@ protected FileSystemInfo(System.Runtime.Serialization.SerializationInfo info, Sy
public System.DateTime LastAccessTimeUtc { get { throw null; } set { } }
public System.DateTime LastWriteTime { get { throw null; } set { } }
public System.DateTime LastWriteTimeUtc { get { throw null; } set { } }
public string? LinkTarget { get { throw null; } }
public abstract string Name { get; }
public void CreateAsSymbolicLink(string pathToTarget) { }
public abstract void Delete();
public virtual void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { }
public void Refresh() { }
public System.IO.FileSystemInfo? ResolveLinkTarget(bool returnFinalTarget = false) { throw null; }
public override string ToString() { throw null; }
}
public sealed partial class DirectoryInfo : System.IO.FileSystemInfo
Expand Down Expand Up @@ -7562,6 +7567,7 @@ public static void Copy(string sourceFileName, string destFileName, bool overwri
public static System.IO.FileStream Create(string path) { throw null; }
public static System.IO.FileStream Create(string path, int bufferSize) { throw null; }
public static System.IO.FileStream Create(string path, int bufferSize, System.IO.FileOptions options) { throw null; }
public static System.IO.FileSystemInfo CreateSymbolicLink(string path, string pathToTarget) { throw null; }
public static System.IO.StreamWriter CreateText(string path) { throw null; }
[System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")]
public static void Decrypt(string path) { }
Expand Down Expand Up @@ -7600,6 +7606,7 @@ public static void Move(string sourceFileName, string destFileName, bool overwri
public static System.Collections.Generic.IEnumerable<string> ReadLines(string path, System.Text.Encoding encoding) { throw null; }
public static void Replace(string sourceFileName, string destinationFileName, string? destinationBackupFileName) { }
public static void Replace(string sourceFileName, string destinationFileName, string? destinationBackupFileName, bool ignoreMetadataErrors) { }
public static System.IO.FileSystemInfo? ResolveLinkTarget(string linkPath, bool returnFinalTarget = false) { throw null; }
public static void SetAttributes(string path, System.IO.FileAttributes fileAttributes) { }
public static void SetCreationTime(string path, System.DateTime creationTime) { }
public static void SetCreationTimeUtc(string path, System.DateTime creationTimeUtc) { }
Expand Down