From a9685f9e55ae02700ad710c6d3f0fa95d00f1a74 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Wed, 24 Sep 2025 20:44:43 +0800 Subject: [PATCH 01/37] Add File hard link API and tests Introduce `File.CreateHardLink` and `FileSystemInfo.CreateAsHardLink` APIs to create hard links, with platform-specific implementations. Add comprehensive tests for hard link creation, behavior, and error cases for both File and FileInfo. Implements #69030 --- .../tests/System/IO/ReparsePointUtilities.cs | 72 ++++++++++++++- .../System.Private.CoreLib.Shared.projitems | 3 + .../src/System/IO/File.cs | 22 +++++ .../src/System/IO/FileSystem.Unix.cs | 5 + .../src/System/IO/FileSystem.Windows.cs | 5 + .../src/System/IO/FileSystemInfo.cs | 19 ++++ .../System.Runtime/ref/System.Runtime.cs | 1 + .../HardLinks/BaseHardLinks.FileSystem.cs | 91 +++++++++++++++++++ .../File/HardLinks.cs | 22 +++++ .../FileInfo/HardLinks.cs | 31 +++++++ .../System.IO.FileSystem.Tests.csproj | 3 + 11 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs create mode 100644 src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/File/HardLinks.cs create mode 100644 src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/HardLinks.cs diff --git a/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs b/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs index 5e9b33146e48d3..013b2035d25812 100644 --- a/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs +++ b/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs @@ -32,6 +32,26 @@ public static partial class MountHelper // Helper for ConditionalClass attributes internal static bool IsSubstAvailable => PlatformDetection.IsSubstAvailable; + /// + /// Verifies that hard link creation is supported on the file system. + /// + internal static bool CanCreateHardLinks => s_canCreateHardLinks.Value; + + private static readonly Lazy s_canCreateHardLinks = new Lazy(() => + { + bool success = true; + + string path = Path.GetTempFileName(); + string linkPath = path + ".link"; + + // Verify file symlink creation + success = CreateHardLink(linkPath: linkPath, targetPath: path); + try { File.Delete(path); } catch { } + try { File.Delete(linkPath); } catch { } + + return success; + }); + /// /// In some cases (such as when running without elevated privileges), /// the symbolic link may fail to create. Only run this test if it creates @@ -65,6 +85,46 @@ public static partial class MountHelper return success; }); + /// Creates a hard link using command line tools. + public static bool CreateHardLink(string linkPath, string targetPath) + { + // It's easy to get the parameters backwards. + Assert.EndsWith(".link", linkPath); + if (linkPath != targetPath) // testing loop + Assert.False(targetPath.EndsWith(".link"), $"{targetPath} should not end with .link"); + +#if NETFRAMEWORK + bool isWindows = true; +#else + if (!IsProcessStartSupported()) + { + return false; + } + + bool isWindows = OperatingSystem.IsWindows(); +#endif + + using Process hardLinkProcess = new Process(); + if (isWindows) + { + hardLinkProcess.StartInfo.FileName = "cmd"; + hardLinkProcess.StartInfo.Arguments = string.Format("/c mklink /H \"{0}\" \"{1}\"", linkPath, targetPath); + } + else + { + hardLinkProcess.StartInfo.FileName = "/bin/ln"; + hardLinkProcess.StartInfo.Arguments = string.Format("\"{0}\" \"{1}\"", targetPath, linkPath); + } + hardLinkProcess.StartInfo.UseShellExecute = false; + hardLinkProcess.StartInfo.RedirectStandardOutput = true; + + hardLinkProcess.Start(); + + hardLinkProcess.WaitForExit(); + + return (hardLinkProcess.ExitCode == 0); + } + /// Creates a symbolic link using command line tools. public static bool CreateSymbolicLink(string linkPath, string targetPath, bool isDirectory) { @@ -76,13 +136,14 @@ public static bool CreateSymbolicLink(string linkPath, string targetPath, bool i #if NETFRAMEWORK bool isWindows = true; #else - if (OperatingSystem.IsIOS() || OperatingSystem.IsTvOS() || OperatingSystem.IsMacCatalyst() || OperatingSystem.IsBrowser() || OperatingSystem.IsWasi()) // OSes that don't support Process.Start() + if (!IsProcessStartSupported()) { return false; } bool isWindows = OperatingSystem.IsWindows(); #endif + using Process symLinkProcess = new Process(); if (isWindows) { @@ -104,6 +165,15 @@ public static bool CreateSymbolicLink(string linkPath, string targetPath, bool i return (symLinkProcess.ExitCode == 0); } + private static bool IsProcessStartSupported() + { +#if NETFRAMEWORK + return true; +#else + return !(OperatingSystem.IsIOS() || OperatingSystem.IsTvOS() || OperatingSystem.IsMacCatalyst() || OperatingSystem.IsBrowser() || OperatingSystem.IsWasi()); // OSes that don't support Process.Start() +#endif + } + /// On Windows, creates a junction using command line tools. public static bool CreateJunction(string junctionPath, string targetPath) { diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 8684386c6e66f5..1699de7c7d8100 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1812,6 +1812,9 @@ Common\Interop\Windows\Kernel32\Interop.CreateFile_IntPtr.cs + + Common\Interop\Windows\Kernel32\Interop.CreateHardLink.cs + Common\Interop\Windows\Kernel32\Interop.CreateSymbolicLink.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index e7c4e948d6c3ce..8b7d2f2861b83c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -1410,6 +1410,28 @@ public static Task AppendAllLinesAsync(string path, IEnumerable contents public static Task AppendAllLinesAsync(string path, IEnumerable contents, Encoding encoding, CancellationToken cancellationToken = default) => WriteAllLinesAsync(path, contents, encoding, append: true, cancellationToken); + /// + /// Establishes a hard link between an existing file and a new file. + /// + /// The path of the new file. + /// The path of the existing file. + /// A instance that wraps the newly created file. + /// or is . + /// or is empty. + /// -or- + /// or contains a null character. + /// A file or directory already exists in the location of . + /// -or- + /// An I/O error occurred. + public static FileSystemInfo CreateHardLink(string path, string pathToTarget) + { + string fullPath = Path.GetFullPath(path); + FileSystem.VerifyValidPath(pathToTarget, nameof(pathToTarget)); + + FileSystem.CreateHardLink(path, pathToTarget); + return new FileInfo(originalPath: path, fullPath: fullPath, isNormalized: true); + } + /// /// Creates a file symbolic link identified by that points to . /// diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs index 69ed0a6016adcf..db94041abb9851 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs @@ -667,6 +667,11 @@ internal static void CreateSymbolicLink(string path, string pathToTarget, bool i } #pragma warning restore IDE0060 + internal static void CreateHardLink(string path, string pathToTarget) + { + Interop.CheckIo(Interop.Sys.Link(pathToTarget, path), path); + } + internal static FileSystemInfo? ResolveLinkTarget(string linkPath, bool returnFinalTarget, bool isDirectory) { ValueStringBuilder sb = new(Interop.DefaultPathBufferSize); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs index e3879ddf6bf651..cc29702789a71f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs @@ -518,6 +518,11 @@ internal static void CreateSymbolicLink(string path, string pathToTarget, bool i Interop.Kernel32.CreateSymbolicLink(path, pathToTarget, isDirectory); } + internal static void CreateHardLink(string path, string pathToTarget) + { + Interop.Kernel32.CreateHardLink(path, pathToTarget); + } + internal static FileSystemInfo? ResolveLinkTarget(string linkPath, bool returnFinalTarget, bool isDirectory) { string? targetPath = returnFinalTarget ? diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs index e8344b6d06f4ba..9ed3881589dc6a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs @@ -165,6 +165,25 @@ public void CreateAsSymbolicLink(string pathToTarget) Invalidate(); } + /// + /// Creates a hard link at that refers to the same file content as . + /// + /// The path of the hard link target. + /// is . + /// is empty. + /// -or- + /// This instance was not created passing an absolute path. + /// -or- + /// contains invalid path characters. + /// A file or directory already exists in the location of . + /// -or- + /// An I/O error occurred. + public void CreateAsHardLink(string pathToTarget) + { + FileSystem.VerifyValidPath(pathToTarget, nameof(pathToTarget)); + FileSystem.CreateHardLink(OriginalPath, pathToTarget); + } + /// /// Gets the target of the specified link. /// diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 0807ee840135d4..f2a3f958dba25e 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -10575,6 +10575,7 @@ protected FileSystemInfo(System.Runtime.Serialization.SerializationInfo info, Sy public abstract string Name { get; } public System.IO.UnixFileMode UnixFileMode { get { throw null; } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] set { } } public void CreateAsSymbolicLink(string pathToTarget) { } + public void CreateAsHardLink(string pathToTarget) { } public abstract void Delete(); [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] [System.ObsoleteAttribute("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.", DiagnosticId="SYSLIB0051", UrlFormat="https://aka.ms/dotnet-warnings/{0}")] diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs new file mode 100644 index 00000000000000..3264e2efe2aeab --- /dev/null +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs @@ -0,0 +1,91 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using Xunit; + +namespace System.IO.Tests +{ + // Contains test methods that can be used for FileInfo or File. + public abstract class BaseHardLinks_FileSystem : FileSystemTest + { + /// Creates a new file depending on the implementing class. + protected abstract void CreateFile(string path); + + protected abstract void AssertLinkExists(FileSystemInfo linkInfo); + + /// Calls the actual public API for creating a hard link. + protected abstract FileSystemInfo CreateHardLink(string path, string pathToTarget); + + [Fact] + public void CreateHardLink_NullPathToTarget() + { + Assert.Throws(() => CreateHardLink(GetRandomFilePath(), pathToTarget: null)); + } + + [Theory] + [InlineData("")] + [InlineData("\0")] + public void CreateHardLink_InvalidPathToTarget(string pathToTarget) + { + Assert.Throws(() => CreateHardLink(GetRandomFilePath(), pathToTarget)); + } + + [Fact] + public void CreateHardLink_TargetDoesNotExist_Throws() + { + string linkPath = GetRandomFilePath(); + string nonExistentTarget = GetRandomFilePath(); + Assert.Throws(() => CreateHardLink(linkPath, nonExistentTarget)); + } + + [Fact] + public void CreateHardLink_TargetExists_Succeeds() + { + string targetPath = GetRandomFilePath(); + string linkPath = GetRandomFilePath(); + + CreateFile(targetPath); + File.WriteAllText(targetPath, "data"); + + FileSystemInfo linkInfo = CreateHardLink(linkPath, targetPath); + AssertLinkExists(linkInfo); + + // Both files should have the same content + Assert.Equal(File.ReadAllText(targetPath), File.ReadAllText(linkPath)); + } + + [Fact] + public void CreateHardLink_ModifyViaOneLink_VisibleViaOther() + { + string targetPath = GetRandomFilePath(); + string linkPath = GetRandomFilePath(); + + File.WriteAllText(targetPath, "original"); + CreateHardLink(linkPath, targetPath); + + // Modify via link + File.WriteAllText(linkPath, "changed"); + + // Read via target + Assert.Equal("changed", File.ReadAllText(targetPath)); + } + + [Fact] + public void CreateHardLink_DeleteOneLink_FileStillAccessible() + { + string targetPath = GetRandomFilePath(); + string linkPath = GetRandomFilePath(); + + File.WriteAllText(targetPath, "data"); + CreateHardLink(linkPath, targetPath); + + // Delete the original file + File.Delete(targetPath); + + // The link should still exist and have the data + Assert.Equal("data", File.ReadAllText(linkPath)); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/File/HardLinks.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/File/HardLinks.cs new file mode 100644 index 00000000000000..708e4b82a37209 --- /dev/null +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/File/HardLinks.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using Xunit; + +namespace System.IO.Tests +{ + [ConditionalClass(typeof(MountHelper), nameof(MountHelper.CanCreateHardLinks))] + public class File_HardLinks : BaseHardLinks_FileSystem + { + protected override void CreateFile(string path) => + File.Create(path).Dispose(); + + protected override void AssertLinkExists(FileSystemInfo linkInfo) => + Assert.True(linkInfo.Exists); + + protected override FileSystemInfo CreateHardLink(string path, string pathToTarget) => + File.CreateHardLink(path, pathToTarget); + } +} diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/HardLinks.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/HardLinks.cs new file mode 100644 index 00000000000000..84487d0d27ed35 --- /dev/null +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/HardLinks.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using Xunit; + +namespace System.IO.Tests +{ + [ConditionalClass(typeof(MountHelper), nameof(MountHelper.CanCreateHardLinks))] + public class FileInfo_HardLinks : BaseHardLinks_FileSystem + { + private FileSystemInfo GetFileSystemInfo(string path) => + new FileInfo(path); + + protected override void CreateFile(string path) => + File.Create(path).Dispose(); + + protected override void AssertLinkExists(FileSystemInfo linkInfo) => + Assert.True(linkInfo.Exists); + + protected override FileSystemInfo CreateHardLink(string path, string pathToTarget) + { + FileSystemInfo link = GetFileSystemInfo(path); + link.CreateAsHardLink(pathToTarget); + return link; + } + + } +} + diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj index 86eda9fae42586..1828a9ffa32e08 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj @@ -27,6 +27,7 @@ + @@ -34,6 +35,7 @@ + @@ -180,6 +182,7 @@ + From 8d8b8ddf8b8396103785fab8deab39b089387d02 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Wed, 24 Sep 2025 21:39:18 +0800 Subject: [PATCH 02/37] Update CompatibilitySuppressions.xml to suppress APICompat --- .../CompatibilitySuppressions.xml | 10 ++++++++++ .../src/CompatibilitySuppressions.xml | 6 ++++++ .../CompatibilitySuppressions.xml | 10 ++++++++++ 3 files changed, 26 insertions(+) create mode 100644 src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml create mode 100644 src/mono/System.Private.CoreLib/CompatibilitySuppressions.xml diff --git a/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml b/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml new file mode 100644 index 00000000000000..ec486b9a5dc91e --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml @@ -0,0 +1,10 @@ + + + + + CP0002 + M:System.IO.File.CreateHardLink(System.String,System.String) + ref/net10.0/System.Private.CoreLib.dll + lib/net10.0/System.Private.CoreLib.dll + + \ No newline at end of file diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index b85912e6cf1ff0..c0621ad4edad40 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -825,4 +825,10 @@ CP0001 T:Internal.NativeFormat.TypeHashingAlgorithms + + CP0002 + M:System.IO.File.CreateHardLink(System.String,System.String) + ref/net10.0/System.Private.CoreLib.dll + lib/net10.0/System.Private.CoreLib.dll + diff --git a/src/mono/System.Private.CoreLib/CompatibilitySuppressions.xml b/src/mono/System.Private.CoreLib/CompatibilitySuppressions.xml new file mode 100644 index 00000000000000..ec486b9a5dc91e --- /dev/null +++ b/src/mono/System.Private.CoreLib/CompatibilitySuppressions.xml @@ -0,0 +1,10 @@ + + + + + CP0002 + M:System.IO.File.CreateHardLink(System.String,System.String) + ref/net10.0/System.Private.CoreLib.dll + lib/net10.0/System.Private.CoreLib.dll + + \ No newline at end of file From e2dc0326ba416d3929d25baf7091201db4b7a68a Mon Sep 17 00:00:00 2001 From: SadPencil Date: Wed, 24 Sep 2025 22:25:05 +0800 Subject: [PATCH 03/37] Have to include unrelated suppressions as well --- .../CompatibilitySuppressions.xml | 30 +++++++++ .../src/CompatibilitySuppressions.xml | 64 ++++++++++++++----- 2 files changed, 77 insertions(+), 17 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml b/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml index ec486b9a5dc91e..cab0935e889bba 100644 --- a/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml +++ b/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml @@ -1,10 +1,40 @@  + + CP0001 + T:Internal.Console + + + CP0002 + F:System.Resources.ResourceManager.BaseNameField + + + CP0002 + F:System.Resources.ResourceSet.Reader + + + CP0002 + M:System.String.Trim(System.ReadOnlySpan{System.Char}) + + + CP0002 + M:System.String.TrimEnd(System.ReadOnlySpan{System.Char}) + + + CP0002 + M:System.String.TrimStart(System.ReadOnlySpan{System.Char}) + CP0002 M:System.IO.File.CreateHardLink(System.String,System.String) ref/net10.0/System.Private.CoreLib.dll lib/net10.0/System.Private.CoreLib.dll + + CP0008 + T:System.Collections.BitArray + ref/net10.0/System.Private.CoreLib.dll + lib/net10.0/System.Private.CoreLib.dll + \ No newline at end of file diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index c0621ad4edad40..496128a3afd015 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -1,6 +1,10 @@  + + CP0001 + T:Internal.Console + CP0001 T:Internal.Metadata.NativeFormat.ArraySignature @@ -53,14 +57,6 @@ CP0001 T:Internal.Metadata.NativeFormat.ConstantBooleanValueHandle - - CP0001 - T:Internal.Metadata.NativeFormat.ConstantEnumValue - - - CP0001 - T:Internal.Metadata.NativeFormat.ConstantEnumValueHandle - CP0001 T:Internal.Metadata.NativeFormat.ConstantByteArray @@ -117,6 +113,14 @@ CP0001 T:Internal.Metadata.NativeFormat.ConstantEnumArrayHandle + + CP0001 + T:Internal.Metadata.NativeFormat.ConstantEnumValue + + + CP0001 + T:Internal.Metadata.NativeFormat.ConstantEnumValueHandle + CP0001 T:Internal.Metadata.NativeFormat.ConstantHandleArray @@ -653,6 +657,10 @@ CP0001 T:Internal.Metadata.NativeFormat.UInt64Collection + + CP0001 + T:Internal.NativeFormat.TypeHashingAlgorithms + CP0001 T:Internal.Reflection.Core.AssemblyBinder @@ -725,6 +733,10 @@ CP0001 T:Internal.TypeSystem.LockFreeReaderHashtable`2 + + CP0001 + T:Internal.TypeSystem.LockFreeReaderHashtableOfPointers`2 + CP0001 T:System.Diagnostics.DebugAnnotations @@ -735,11 +747,11 @@ CP0001 - T:System.MDArray + T:System.FieldHandleInfo CP0001 - T:System.FieldHandleInfo + T:System.MDArray CP0001 @@ -806,8 +818,16 @@ T:System.Runtime.CompilerServices.StaticClassConstructionContext - CP0001 - T:Internal.TypeSystem.LockFreeReaderHashtableOfPointers`2 + CP0002 + F:System.Resources.ResourceManager.BaseNameField + + + CP0002 + F:System.Resources.ResourceSet.Reader + + + CP0002 + M:System.Diagnostics.DiagnosticMethodInfo.#ctor(System.String,System.String,System.String) CP0002 @@ -815,15 +835,19 @@ CP0002 - M:System.Threading.Lock.#ctor(System.Boolean) + M:System.String.Trim(System.ReadOnlySpan{System.Char}) CP0002 - M:System.Diagnostics.DiagnosticMethodInfo.#ctor(System.String,System.String,System.String) + M:System.String.TrimEnd(System.ReadOnlySpan{System.Char}) - CP0001 - T:Internal.NativeFormat.TypeHashingAlgorithms + CP0002 + M:System.String.TrimStart(System.ReadOnlySpan{System.Char}) + + + CP0002 + M:System.Threading.Lock.#ctor(System.Boolean) CP0002 @@ -831,4 +855,10 @@ ref/net10.0/System.Private.CoreLib.dll lib/net10.0/System.Private.CoreLib.dll - + + CP0008 + T:System.Collections.BitArray + ref/net10.0/System.Private.CoreLib.dll + lib/net10.0/System.Private.CoreLib.dll + + \ No newline at end of file From eb72e2abc16eafa0afbbb9ddda2960e2c4db32fe Mon Sep 17 00:00:00 2001 From: SadPencil Date: Thu, 25 Sep 2025 00:23:39 +0800 Subject: [PATCH 04/37] Revert "Have to include unrelated suppressions as well" This reverts commit e2dc0326ba416d3929d25baf7091201db4b7a68a. --- .../CompatibilitySuppressions.xml | 30 --------- .../src/CompatibilitySuppressions.xml | 64 +++++-------------- 2 files changed, 17 insertions(+), 77 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml b/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml index cab0935e889bba..ec486b9a5dc91e 100644 --- a/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml +++ b/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml @@ -1,40 +1,10 @@  - - CP0001 - T:Internal.Console - - - CP0002 - F:System.Resources.ResourceManager.BaseNameField - - - CP0002 - F:System.Resources.ResourceSet.Reader - - - CP0002 - M:System.String.Trim(System.ReadOnlySpan{System.Char}) - - - CP0002 - M:System.String.TrimEnd(System.ReadOnlySpan{System.Char}) - - - CP0002 - M:System.String.TrimStart(System.ReadOnlySpan{System.Char}) - CP0002 M:System.IO.File.CreateHardLink(System.String,System.String) ref/net10.0/System.Private.CoreLib.dll lib/net10.0/System.Private.CoreLib.dll - - CP0008 - T:System.Collections.BitArray - ref/net10.0/System.Private.CoreLib.dll - lib/net10.0/System.Private.CoreLib.dll - \ No newline at end of file diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index 496128a3afd015..c0621ad4edad40 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -1,10 +1,6 @@  - - CP0001 - T:Internal.Console - CP0001 T:Internal.Metadata.NativeFormat.ArraySignature @@ -57,6 +53,14 @@ CP0001 T:Internal.Metadata.NativeFormat.ConstantBooleanValueHandle + + CP0001 + T:Internal.Metadata.NativeFormat.ConstantEnumValue + + + CP0001 + T:Internal.Metadata.NativeFormat.ConstantEnumValueHandle + CP0001 T:Internal.Metadata.NativeFormat.ConstantByteArray @@ -113,14 +117,6 @@ CP0001 T:Internal.Metadata.NativeFormat.ConstantEnumArrayHandle - - CP0001 - T:Internal.Metadata.NativeFormat.ConstantEnumValue - - - CP0001 - T:Internal.Metadata.NativeFormat.ConstantEnumValueHandle - CP0001 T:Internal.Metadata.NativeFormat.ConstantHandleArray @@ -657,10 +653,6 @@ CP0001 T:Internal.Metadata.NativeFormat.UInt64Collection - - CP0001 - T:Internal.NativeFormat.TypeHashingAlgorithms - CP0001 T:Internal.Reflection.Core.AssemblyBinder @@ -733,10 +725,6 @@ CP0001 T:Internal.TypeSystem.LockFreeReaderHashtable`2 - - CP0001 - T:Internal.TypeSystem.LockFreeReaderHashtableOfPointers`2 - CP0001 T:System.Diagnostics.DebugAnnotations @@ -747,11 +735,11 @@ CP0001 - T:System.FieldHandleInfo + T:System.MDArray CP0001 - T:System.MDArray + T:System.FieldHandleInfo CP0001 @@ -818,16 +806,8 @@ T:System.Runtime.CompilerServices.StaticClassConstructionContext - CP0002 - F:System.Resources.ResourceManager.BaseNameField - - - CP0002 - F:System.Resources.ResourceSet.Reader - - - CP0002 - M:System.Diagnostics.DiagnosticMethodInfo.#ctor(System.String,System.String,System.String) + CP0001 + T:Internal.TypeSystem.LockFreeReaderHashtableOfPointers`2 CP0002 @@ -835,19 +815,15 @@ CP0002 - M:System.String.Trim(System.ReadOnlySpan{System.Char}) - - - CP0002 - M:System.String.TrimEnd(System.ReadOnlySpan{System.Char}) + M:System.Threading.Lock.#ctor(System.Boolean) CP0002 - M:System.String.TrimStart(System.ReadOnlySpan{System.Char}) + M:System.Diagnostics.DiagnosticMethodInfo.#ctor(System.String,System.String,System.String) - CP0002 - M:System.Threading.Lock.#ctor(System.Boolean) + CP0001 + T:Internal.NativeFormat.TypeHashingAlgorithms CP0002 @@ -855,10 +831,4 @@ ref/net10.0/System.Private.CoreLib.dll lib/net10.0/System.Private.CoreLib.dll - - CP0008 - T:System.Collections.BitArray - ref/net10.0/System.Private.CoreLib.dll - lib/net10.0/System.Private.CoreLib.dll - - \ No newline at end of file + From a1d1196288915da723ae5b7b789207cccfe39ee6 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Thu, 25 Sep 2025 00:24:16 +0800 Subject: [PATCH 05/37] Revert "Update CompatibilitySuppressions.xml to suppress APICompat" This reverts commit 8d8b8ddf8b8396103785fab8deab39b089387d02. --- .../CompatibilitySuppressions.xml | 10 ---------- .../src/CompatibilitySuppressions.xml | 6 ------ .../CompatibilitySuppressions.xml | 10 ---------- 3 files changed, 26 deletions(-) delete mode 100644 src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml delete mode 100644 src/mono/System.Private.CoreLib/CompatibilitySuppressions.xml diff --git a/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml b/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml deleted file mode 100644 index ec486b9a5dc91e..00000000000000 --- a/src/coreclr/System.Private.CoreLib/CompatibilitySuppressions.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - CP0002 - M:System.IO.File.CreateHardLink(System.String,System.String) - ref/net10.0/System.Private.CoreLib.dll - lib/net10.0/System.Private.CoreLib.dll - - \ No newline at end of file diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml index c0621ad4edad40..b85912e6cf1ff0 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/CompatibilitySuppressions.xml @@ -825,10 +825,4 @@ CP0001 T:Internal.NativeFormat.TypeHashingAlgorithms - - CP0002 - M:System.IO.File.CreateHardLink(System.String,System.String) - ref/net10.0/System.Private.CoreLib.dll - lib/net10.0/System.Private.CoreLib.dll - diff --git a/src/mono/System.Private.CoreLib/CompatibilitySuppressions.xml b/src/mono/System.Private.CoreLib/CompatibilitySuppressions.xml deleted file mode 100644 index ec486b9a5dc91e..00000000000000 --- a/src/mono/System.Private.CoreLib/CompatibilitySuppressions.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - CP0002 - M:System.IO.File.CreateHardLink(System.String,System.String) - ref/net10.0/System.Private.CoreLib.dll - lib/net10.0/System.Private.CoreLib.dll - - \ No newline at end of file From 944df06f6cee3428c1a188496ffde3cf085e0499 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Thu, 25 Sep 2025 00:25:03 +0800 Subject: [PATCH 06/37] Correct the expected exception in the hardlink test --- .../Base/HardLinks/BaseHardLinks.FileSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs index 3264e2efe2aeab..b3678219bc4a7e 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs @@ -37,7 +37,7 @@ public void CreateHardLink_TargetDoesNotExist_Throws() { string linkPath = GetRandomFilePath(); string nonExistentTarget = GetRandomFilePath(); - Assert.Throws(() => CreateHardLink(linkPath, nonExistentTarget)); + Assert.Throws(() => CreateHardLink(linkPath, nonExistentTarget)); } [Fact] From aa42eb477c110c69c4b586091c10576c61df10f0 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Thu, 25 Sep 2025 00:36:57 +0800 Subject: [PATCH 07/37] Add CreateHardLink to ref --- src/libraries/System.Runtime/ref/System.Runtime.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index f2a3f958dba25e..264912e58e2931 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -10258,6 +10258,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 CreateHardLink(string path, string pathToTarget) { 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")] From 5536a867924394c0ea913e0228d0484b4152d84e Mon Sep 17 00:00:00 2001 From: SadPencil Date: Thu, 25 Sep 2025 01:26:52 +0800 Subject: [PATCH 08/37] Address incorrect comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs b/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs index 013b2035d25812..e31f51da320aa6 100644 --- a/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs +++ b/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs @@ -44,7 +44,7 @@ public static partial class MountHelper string path = Path.GetTempFileName(); string linkPath = path + ".link"; - // Verify file symlink creation + // Verify file hard link creation success = CreateHardLink(linkPath: linkPath, targetPath: path); try { File.Delete(path); } catch { } try { File.Delete(linkPath); } catch { } From 941730d28d1dd6539cae5a5e9c5ee49c0ecfa21d Mon Sep 17 00:00:00 2001 From: SadPencil Date: Thu, 25 Sep 2025 08:50:11 +0800 Subject: [PATCH 09/37] Reorder code blocks No functional changes. --- .../tests/System/IO/ReparsePointUtilities.cs | 3 +-- .../src/System/IO/FileSystemInfo.cs | 18 +++++++++--------- .../System.Runtime/ref/System.Runtime.cs | 2 +- .../System.IO.FileSystem.Tests.csproj | 2 +- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs b/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs index e31f51da320aa6..c570fd0fd7ca93 100644 --- a/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs +++ b/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs @@ -41,10 +41,9 @@ public static partial class MountHelper { bool success = true; + // Verify file hard link creation string path = Path.GetTempFileName(); string linkPath = path + ".link"; - - // Verify file hard link creation success = CreateHardLink(linkPath: linkPath, targetPath: path); try { File.Delete(path); } catch { } try { File.Delete(linkPath); } catch { } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs index 9ed3881589dc6a..80ddd8401fda7c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs @@ -146,9 +146,9 @@ public UnixFileMode UnixFileMode } /// - /// Creates a symbolic link located in that points to the specified . + /// Creates a hard link located in that refers to the same file content as . /// - /// The path of the symbolic link target. + /// The path of the hard link target. /// is . /// is empty. /// -or- @@ -158,17 +158,16 @@ public UnixFileMode UnixFileMode /// A file or directory already exists in the location of . /// -or- /// An I/O error occurred. - public void CreateAsSymbolicLink(string pathToTarget) + public void CreateAsHardLink(string pathToTarget) { FileSystem.VerifyValidPath(pathToTarget, nameof(pathToTarget)); - FileSystem.CreateSymbolicLink(OriginalPath, pathToTarget, this is DirectoryInfo); - Invalidate(); + FileSystem.CreateHardLink(OriginalPath, pathToTarget); } /// - /// Creates a hard link at that refers to the same file content as . + /// Creates a symbolic link located in that points to the specified . /// - /// The path of the hard link target. + /// The path of the symbolic link target. /// is . /// is empty. /// -or- @@ -178,10 +177,11 @@ public void CreateAsSymbolicLink(string pathToTarget) /// A file or directory already exists in the location of . /// -or- /// An I/O error occurred. - public void CreateAsHardLink(string pathToTarget) + public void CreateAsSymbolicLink(string pathToTarget) { FileSystem.VerifyValidPath(pathToTarget, nameof(pathToTarget)); - FileSystem.CreateHardLink(OriginalPath, pathToTarget); + FileSystem.CreateSymbolicLink(OriginalPath, pathToTarget, this is DirectoryInfo); + Invalidate(); } /// diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 264912e58e2931..7e623c1a75e304 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -10575,8 +10575,8 @@ protected FileSystemInfo(System.Runtime.Serialization.SerializationInfo info, Sy public string? LinkTarget { get { throw null; } } public abstract string Name { get; } public System.IO.UnixFileMode UnixFileMode { get { throw null; } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] set { } } - public void CreateAsSymbolicLink(string pathToTarget) { } public void CreateAsHardLink(string pathToTarget) { } + public void CreateAsSymbolicLink(string pathToTarget) { } public abstract void Delete(); [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] [System.ObsoleteAttribute("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.", DiagnosticId="SYSLIB0051", UrlFormat="https://aka.ms/dotnet-warnings/{0}")] diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj index 1828a9ffa32e08..de9573d7892dbe 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/System.IO.FileSystem.Tests.csproj @@ -24,10 +24,10 @@ + - From ea28a89990b69aee33d07ee7913ebc5ae05de413 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Thu, 25 Sep 2025 08:57:54 +0800 Subject: [PATCH 10/37] Slightly update the hard link test Calling `File.WriteAllText` (creates the file if it does not exist) right after `CreateFile` might be redundant --- .../Base/HardLinks/BaseHardLinks.FileSystem.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs index b3678219bc4a7e..497649f74cd03e 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs @@ -47,7 +47,6 @@ public void CreateHardLink_TargetExists_Succeeds() string linkPath = GetRandomFilePath(); CreateFile(targetPath); - File.WriteAllText(targetPath, "data"); FileSystemInfo linkInfo = CreateHardLink(linkPath, targetPath); AssertLinkExists(linkInfo); From 613d944c27266fe6a472a3f5686bac181208907b Mon Sep 17 00:00:00 2001 From: SadPencil Date: Thu, 25 Sep 2025 09:23:51 +0800 Subject: [PATCH 11/37] Add mount helper assert to the base hard link test class --- .../Base/HardLinks/BaseHardLinks.FileSystem.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs index 497649f74cd03e..36d6b251b2d9c3 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs @@ -10,6 +10,11 @@ namespace System.IO.Tests // Contains test methods that can be used for FileInfo or File. public abstract class BaseHardLinks_FileSystem : FileSystemTest { + public BaseHardLinks_FileSystem() + { + Assert.True(MountHelper.CanCreateHardLinks); + } + /// Creates a new file depending on the implementing class. protected abstract void CreateFile(string path); From c43c1c1fd8725411e7616acfd421d9936c286fd0 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Sat, 11 Oct 2025 13:55:45 +0800 Subject: [PATCH 12/37] Apply suggestion from @jozkee MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Cantú --- src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs b/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs index c570fd0fd7ca93..5906341eeeec06 100644 --- a/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs +++ b/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs @@ -85,7 +85,7 @@ public static partial class MountHelper }); /// Creates a hard link using command line tools. - public static bool CreateHardLink(string linkPath, string targetPath) + private static bool CreateHardLink(string linkPath, string targetPath) { // It's easy to get the parameters backwards. Assert.EndsWith(".link", linkPath); From 12ab897bf2ab8ae81a425b2b771a7ab9703474c2 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Sat, 11 Oct 2025 13:56:11 +0800 Subject: [PATCH 13/37] Apply suggestion from @jozkee MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Cantú --- src/libraries/System.Private.CoreLib/src/System/IO/File.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index 8b7d2f2861b83c..2bce3eaf72f398 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -1414,7 +1414,7 @@ public static Task AppendAllLinesAsync(string path, IEnumerable contents /// Establishes a hard link between an existing file and a new file. /// /// The path of the new file. - /// The path of the existing file. + /// The path of the hard link target. /// A instance that wraps the newly created file. /// or is . /// or is empty. From df34da90647fda0862c5dbf85c21724f16abbc05 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Sat, 11 Oct 2025 13:56:20 +0800 Subject: [PATCH 14/37] Apply suggestion from @jozkee MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Cantú --- src/libraries/System.Private.CoreLib/src/System/IO/File.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index 2bce3eaf72f398..6c396ad884c92e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -1413,7 +1413,7 @@ public static Task AppendAllLinesAsync(string path, IEnumerable contents /// /// Establishes a hard link between an existing file and a new file. /// - /// The path of the new file. + /// The path where the hard link should be created. /// The path of the hard link target. /// A instance that wraps the newly created file. /// or is . From 8b1bdbb93c958b6309b05362b67f7b60f86b9831 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Sat, 11 Oct 2025 14:01:23 +0800 Subject: [PATCH 15/37] Apply suggestion from @jozkee MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Cantú --- src/libraries/System.Private.CoreLib/src/System/IO/File.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index 6c396ad884c92e..f9e6ee4b0d8b65 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -1411,7 +1411,7 @@ public static Task AppendAllLinesAsync(string path, IEnumerable contents WriteAllLinesAsync(path, contents, encoding, append: true, cancellationToken); /// - /// Establishes a hard link between an existing file and a new file. + /// Creates a hard link located in that refers to the same file content as . /// /// The path where the hard link should be created. /// The path of the hard link target. From f374e8c460880bd2d8a9285bc1da5ff5d533f3e6 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Sat, 11 Oct 2025 14:23:56 +0800 Subject: [PATCH 16/37] Add CreateHardLink_LinkPathAlreadyExists_Throws test --- .../Base/HardLinks/BaseHardLinks.FileSystem.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs index 36d6b251b2d9c3..4e65fbb2009927 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs @@ -45,6 +45,18 @@ public void CreateHardLink_TargetDoesNotExist_Throws() Assert.Throws(() => CreateHardLink(linkPath, nonExistentTarget)); } + [Fact] + public void CreateHardLink_LinkPathAlreadyExists_Throws() + { + string targetPath = GetRandomFilePath(); + string linkPath = GetRandomFilePath(); + + CreateFile(targetPath); + CreateFile(linkPath); + + Assert.Throws(() => CreateHardLink(linkPath, targetPath)); + } + [Fact] public void CreateHardLink_TargetExists_Succeeds() { From a1759195601a8900e195c690762b33b095e8972a Mon Sep 17 00:00:00 2001 From: SadPencil Date: Sat, 11 Oct 2025 14:31:24 +0800 Subject: [PATCH 17/37] Move CreateAsHardLink method from FileSystemInfo to FileInfo --- .../src/System/IO/FileInfo.cs | 19 +++++++++++++++++++ .../src/System/IO/FileSystemInfo.cs | 19 ------------------- .../System.Runtime/ref/System.Runtime.cs | 2 +- .../FileInfo/HardLinks.cs | 4 ++-- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.cs index f752f64e8e0d58..a32afc2215a1fc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.cs @@ -204,5 +204,24 @@ private StreamWriter CreateStreamWriter(bool append) Invalidate(); return streamWriter; } + + /// + /// Creates a hard link located in that refers to the same file content as . + /// + /// The path of the hard link target. + /// is . + /// is empty. + /// -or- + /// This instance was not created passing an absolute path. + /// -or- + /// contains invalid path characters. + /// A file or directory already exists in the location of . + /// -or- + /// An I/O error occurred. + public void CreateAsHardLink(string pathToTarget) + { + FileSystem.VerifyValidPath(pathToTarget, nameof(pathToTarget)); + FileSystem.CreateHardLink(OriginalPath, pathToTarget); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs index 80ddd8401fda7c..e8344b6d06f4ba 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystemInfo.cs @@ -145,25 +145,6 @@ public UnixFileMode UnixFileMode set => UnixFileModeCore = value; } - /// - /// Creates a hard link located in that refers to the same file content as . - /// - /// The path of the hard link target. - /// is . - /// is empty. - /// -or- - /// This instance was not created passing an absolute path. - /// -or- - /// contains invalid path characters. - /// A file or directory already exists in the location of . - /// -or- - /// An I/O error occurred. - public void CreateAsHardLink(string pathToTarget) - { - FileSystem.VerifyValidPath(pathToTarget, nameof(pathToTarget)); - FileSystem.CreateHardLink(OriginalPath, pathToTarget); - } - /// /// Creates a symbolic link located in that points to the specified . /// diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 7e623c1a75e304..55c555213facba 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -10407,6 +10407,7 @@ public void MoveTo(string destFileName, bool overwrite) { } public System.IO.FileStream OpenWrite() { throw null; } public System.IO.FileInfo Replace(string destinationFileName, string? destinationBackupFileName) { throw null; } public System.IO.FileInfo Replace(string destinationFileName, string? destinationBackupFileName, bool ignoreMetadataErrors) { throw null; } + public void CreateAsHardLink(string pathToTarget) { } } public partial class FileLoadException : System.IO.IOException { @@ -10575,7 +10576,6 @@ protected FileSystemInfo(System.Runtime.Serialization.SerializationInfo info, Sy public string? LinkTarget { get { throw null; } } public abstract string Name { get; } public System.IO.UnixFileMode UnixFileMode { get { throw null; } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] set { } } - public void CreateAsHardLink(string pathToTarget) { } public void CreateAsSymbolicLink(string pathToTarget) { } public abstract void Delete(); [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/HardLinks.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/HardLinks.cs index 84487d0d27ed35..040ba2e12bfa8f 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/HardLinks.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/HardLinks.cs @@ -10,7 +10,7 @@ namespace System.IO.Tests [ConditionalClass(typeof(MountHelper), nameof(MountHelper.CanCreateHardLinks))] public class FileInfo_HardLinks : BaseHardLinks_FileSystem { - private FileSystemInfo GetFileSystemInfo(string path) => + private FileInfo GetFileSystemInfo(string path) => new FileInfo(path); protected override void CreateFile(string path) => @@ -21,7 +21,7 @@ protected override void AssertLinkExists(FileSystemInfo linkInfo) => protected override FileSystemInfo CreateHardLink(string path, string pathToTarget) { - FileSystemInfo link = GetFileSystemInfo(path); + FileInfo link = GetFileSystemInfo(path); link.CreateAsHardLink(pathToTarget); return link; } From b70714f0a6d6a71579937c38a70ba691170f6f3d Mon Sep 17 00:00:00 2001 From: SadPencil Date: Sat, 11 Oct 2025 14:43:08 +0800 Subject: [PATCH 18/37] Remove GetFileSystemInfo method in FileInfo_HardLinks test --- .../tests/System.IO.FileSystem.Tests/FileInfo/HardLinks.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/HardLinks.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/HardLinks.cs index 040ba2e12bfa8f..75c123fa045533 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/HardLinks.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/HardLinks.cs @@ -10,9 +10,6 @@ namespace System.IO.Tests [ConditionalClass(typeof(MountHelper), nameof(MountHelper.CanCreateHardLinks))] public class FileInfo_HardLinks : BaseHardLinks_FileSystem { - private FileInfo GetFileSystemInfo(string path) => - new FileInfo(path); - protected override void CreateFile(string path) => File.Create(path).Dispose(); @@ -21,7 +18,7 @@ protected override void AssertLinkExists(FileSystemInfo linkInfo) => protected override FileSystemInfo CreateHardLink(string path, string pathToTarget) { - FileInfo link = GetFileSystemInfo(path); + FileInfo link = new FileInfo(path); link.CreateAsHardLink(pathToTarget); return link; } From 2e39f9b5bac03137af902efc0e31aed36f182f1a Mon Sep 17 00:00:00 2001 From: SadPencil Date: Sat, 11 Oct 2025 14:45:01 +0800 Subject: [PATCH 19/37] Revert "Apply suggestion from @jozkee" This reverts commit c43c1c1fd8725411e7616acfd421d9936c286fd0. --- src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs b/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs index 5906341eeeec06..c570fd0fd7ca93 100644 --- a/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs +++ b/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs @@ -85,7 +85,7 @@ public static partial class MountHelper }); /// Creates a hard link using command line tools. - private static bool CreateHardLink(string linkPath, string targetPath) + public static bool CreateHardLink(string linkPath, string targetPath) { // It's easy to get the parameters backwards. Assert.EndsWith(".link", linkPath); From eb318f32848981186fcb311181b6959b7b59967f Mon Sep 17 00:00:00 2001 From: SadPencil Date: Sun, 12 Oct 2025 00:03:37 +0800 Subject: [PATCH 20/37] Sort the CreateAsHardLink method in the ref file of System.Runtime --- src/libraries/System.Runtime/ref/System.Runtime.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 55c555213facba..685c87144f1d98 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -10390,6 +10390,7 @@ public FileInfo(string fileName) { } public System.IO.FileInfo CopyTo(string destFileName) { throw null; } public System.IO.FileInfo CopyTo(string destFileName, bool overwrite) { throw null; } public System.IO.FileStream Create() { throw null; } + public void CreateAsHardLink(string pathToTarget) { } public System.IO.StreamWriter CreateText() { throw null; } [System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")] public void Decrypt() { } @@ -10407,7 +10408,6 @@ public void MoveTo(string destFileName, bool overwrite) { } public System.IO.FileStream OpenWrite() { throw null; } public System.IO.FileInfo Replace(string destinationFileName, string? destinationBackupFileName) { throw null; } public System.IO.FileInfo Replace(string destinationFileName, string? destinationBackupFileName, bool ignoreMetadataErrors) { throw null; } - public void CreateAsHardLink(string pathToTarget) { } } public partial class FileLoadException : System.IO.IOException { From cdd3091e1378c585946fef537a0a4756b6cbbfbd Mon Sep 17 00:00:00 2001 From: SadPencil Date: Mon, 20 Oct 2025 23:36:28 +0800 Subject: [PATCH 21/37] Use File.CreateHardLink() in System.Formats.Tar(.Tests) --- src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj | 1 - .../System.Formats.Tar/src/System/Formats/Tar/TarEntry.Unix.cs | 2 +- .../src/System/Formats/Tar/TarEntry.Windows.cs | 3 ++- .../System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj b/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj index 465e91734c08aa..54671682d1454a 100644 --- a/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj +++ b/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj @@ -47,7 +47,6 @@ - diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.Unix.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.Unix.cs index 95158c000fd7fa..d7c1777c96fe7c 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.Unix.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.Unix.cs @@ -37,7 +37,7 @@ private void ExtractAsHardLink(string targetFilePath, string hardLinkFilePath) Debug.Assert(EntryType is TarEntryType.HardLink); Debug.Assert(!string.IsNullOrEmpty(targetFilePath)); Debug.Assert(!string.IsNullOrEmpty(hardLinkFilePath)); - Interop.CheckIo(Interop.Sys.Link(targetFilePath, hardLinkFilePath), hardLinkFilePath); + File.CreateHardLink(hardLinkFilePath, targetFilePath); } } } diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.Windows.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.Windows.cs index 17d48681719988..c0767922842fff 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.Windows.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.Windows.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.IO; using Microsoft.Win32.SafeHandles; namespace System.Formats.Tar @@ -37,7 +38,7 @@ private void ExtractAsHardLink(string targetFilePath, string hardLinkFilePath) Debug.Assert(EntryType is TarEntryType.HardLink); Debug.Assert(!string.IsNullOrEmpty(targetFilePath)); Debug.Assert(!string.IsNullOrEmpty(hardLinkFilePath)); - Interop.Kernel32.CreateHardLink(hardLinkFilePath, targetFilePath); + File.CreateHardLink(hardLinkFilePath, targetFilePath); } #pragma warning restore IDE0060 } diff --git a/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj b/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj index 144749edded032..4a04663c39dfb5 100644 --- a/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj +++ b/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj @@ -80,7 +80,6 @@ - From 064f9801f5699bcdbf40a62ad274f3340028513e Mon Sep 17 00:00:00 2001 From: SadPencil Date: Mon, 20 Oct 2025 23:37:49 +0800 Subject: [PATCH 22/37] Apply suggestion from @adamsitnik Co-authored-by: Adam Sitnik --- src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs b/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs index c570fd0fd7ca93..d09c9b9a7568f0 100644 --- a/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs +++ b/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs @@ -121,7 +121,7 @@ public static bool CreateHardLink(string linkPath, string targetPath) hardLinkProcess.WaitForExit(); - return (hardLinkProcess.ExitCode == 0); + return hardLinkProcess.ExitCode == 0; } /// Creates a symbolic link using command line tools. From 4705f9f0c95cb5f935b198bad7b477c5578d4bf8 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Mon, 20 Oct 2025 23:38:00 +0800 Subject: [PATCH 23/37] Apply suggestion from @adamsitnik Co-authored-by: Adam Sitnik --- .../Base/HardLinks/BaseHardLinks.FileSystem.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs index 4e65fbb2009927..ea392486651b81 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs @@ -8,12 +8,9 @@ namespace System.IO.Tests { // Contains test methods that can be used for FileInfo or File. + [ConditionalClass(typeof(MountHelper), nameof(MountHelper.CanCreateHardLinks))] public abstract class BaseHardLinks_FileSystem : FileSystemTest { - public BaseHardLinks_FileSystem() - { - Assert.True(MountHelper.CanCreateHardLinks); - } /// Creates a new file depending on the implementing class. protected abstract void CreateFile(string path); From b035c58e20a4bbbbaa7069e153713fbafa6382bf Mon Sep 17 00:00:00 2001 From: SadPencil Date: Tue, 21 Oct 2025 00:10:26 +0800 Subject: [PATCH 24/37] Remove unused includes in System.Formats.Tar(.Tests) --- .../System.Formats.Tar/src/System.Formats.Tar.csproj | 4 ---- .../System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj | 4 ---- 2 files changed, 8 deletions(-) diff --git a/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj b/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj index 54671682d1454a..23c69846b94583 100644 --- a/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj +++ b/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj @@ -45,12 +45,8 @@ - - - - diff --git a/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj b/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj index 4a04663c39dfb5..793ad21095000d 100644 --- a/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj +++ b/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj @@ -78,11 +78,7 @@ - - - - From eb968ac9912c01421a89903023799f6fb4097b31 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Tue, 21 Oct 2025 00:13:37 +0800 Subject: [PATCH 25/37] Remove another unused include in System.Formats.Tar.Tests --- .../System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj b/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj index 793ad21095000d..2468dbe20d768f 100644 --- a/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj +++ b/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj @@ -77,7 +77,6 @@ - From ce6145ff6851e75d1660f1d23dfff86d77927e93 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Tue, 21 Oct 2025 00:22:50 +0800 Subject: [PATCH 26/37] Remove the unwanted empty line in BaseHardLinks_FileSystem --- .../Base/HardLinks/BaseHardLinks.FileSystem.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs index ea392486651b81..4be04fd043dbc3 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs @@ -11,7 +11,6 @@ namespace System.IO.Tests [ConditionalClass(typeof(MountHelper), nameof(MountHelper.CanCreateHardLinks))] public abstract class BaseHardLinks_FileSystem : FileSystemTest { - /// Creates a new file depending on the implementing class. protected abstract void CreateFile(string path); From 1eeb5f9de3c192714f2ff646f167fb7368719fa8 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Tue, 21 Oct 2025 00:26:48 +0800 Subject: [PATCH 27/37] Add clean up for test files in HardLinks tests --- .../Base/HardLinks/BaseHardLinks.FileSystem.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs index 4be04fd043dbc3..e314f1a4d3c13e 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs @@ -50,7 +50,12 @@ public void CreateHardLink_LinkPathAlreadyExists_Throws() CreateFile(targetPath); CreateFile(linkPath); - Assert.Throws(() => CreateHardLink(linkPath, targetPath)); + Assert.Throws(() => { + CreateHardLink(linkPath, targetPath); + try { File.Delete(linkPath); } catch { } + }); + + try { File.Delete(targetPath); } catch { } } [Fact] @@ -66,6 +71,9 @@ public void CreateHardLink_TargetExists_Succeeds() // Both files should have the same content Assert.Equal(File.ReadAllText(targetPath), File.ReadAllText(linkPath)); + + try { File.Delete(linkPath); } catch { } + try { File.Delete(targetPath); } catch { } } [Fact] @@ -82,6 +90,9 @@ public void CreateHardLink_ModifyViaOneLink_VisibleViaOther() // Read via target Assert.Equal("changed", File.ReadAllText(targetPath)); + + try { File.Delete(linkPath); } catch { } + try { File.Delete(targetPath); } catch { } } [Fact] @@ -98,6 +109,8 @@ public void CreateHardLink_DeleteOneLink_FileStillAccessible() // The link should still exist and have the data Assert.Equal("data", File.ReadAllText(linkPath)); + + try { File.Delete(linkPath); } catch { } } } } From 9c837865e38200a3f0f3fe2bdd61325ad2528742 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Tue, 21 Oct 2025 00:30:04 +0800 Subject: [PATCH 28/37] Add another clean up for test files in HardLinks tests --- .../Base/HardLinks/BaseHardLinks.FileSystem.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs index e314f1a4d3c13e..1e049c2069825d 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs @@ -38,7 +38,10 @@ public void CreateHardLink_TargetDoesNotExist_Throws() { string linkPath = GetRandomFilePath(); string nonExistentTarget = GetRandomFilePath(); - Assert.Throws(() => CreateHardLink(linkPath, nonExistentTarget)); + Assert.Throws(() => { + CreateHardLink(linkPath, nonExistentTarget); + try { File.Delete(linkPath); } catch { } + }); } [Fact] From 7b243d6a12537344aad4db9377d727868cf5f5b3 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Tue, 21 Oct 2025 00:31:05 +0800 Subject: [PATCH 29/37] Format BaseHardLinks.FileSystem.cs files --- .../Base/HardLinks/BaseHardLinks.FileSystem.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs index 1e049c2069825d..344379018ee691 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs @@ -38,8 +38,9 @@ public void CreateHardLink_TargetDoesNotExist_Throws() { string linkPath = GetRandomFilePath(); string nonExistentTarget = GetRandomFilePath(); - Assert.Throws(() => { - CreateHardLink(linkPath, nonExistentTarget); + Assert.Throws(() => + { + CreateHardLink(linkPath, nonExistentTarget); try { File.Delete(linkPath); } catch { } }); } @@ -53,7 +54,8 @@ public void CreateHardLink_LinkPathAlreadyExists_Throws() CreateFile(targetPath); CreateFile(linkPath); - Assert.Throws(() => { + Assert.Throws(() => + { CreateHardLink(linkPath, targetPath); try { File.Delete(linkPath); } catch { } }); From 9ab3baece06a2601d9735834ede015fc800bfb46 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Tue, 21 Oct 2025 00:42:36 +0800 Subject: [PATCH 30/37] Remove the conditional attribute for the child classes of BaseHardLinks_FileSystem --- .../tests/System.IO.FileSystem.Tests/File/HardLinks.cs | 1 - .../tests/System.IO.FileSystem.Tests/FileInfo/HardLinks.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/File/HardLinks.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/File/HardLinks.cs index 708e4b82a37209..e67c994d401801 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/File/HardLinks.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/File/HardLinks.cs @@ -7,7 +7,6 @@ namespace System.IO.Tests { - [ConditionalClass(typeof(MountHelper), nameof(MountHelper.CanCreateHardLinks))] public class File_HardLinks : BaseHardLinks_FileSystem { protected override void CreateFile(string path) => diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/HardLinks.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/HardLinks.cs index 75c123fa045533..591c38d8acc457 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/HardLinks.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/FileInfo/HardLinks.cs @@ -7,7 +7,6 @@ namespace System.IO.Tests { - [ConditionalClass(typeof(MountHelper), nameof(MountHelper.CanCreateHardLinks))] public class FileInfo_HardLinks : BaseHardLinks_FileSystem { protected override void CreateFile(string path) => From e09869a840c58cf6a81c59fe26e15b149b4c7572 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Tue, 21 Oct 2025 00:46:44 +0800 Subject: [PATCH 31/37] Remove the conditional attribute for the child classes of BaseSymbolicLinks --- .../Base/SymbolicLinks/BaseSymbolicLinks.cs | 6 +----- .../Enumeration/SymbolicLinksTests.cs | 1 - .../tests/System.IO.FileSystem.Tests/Junctions.Windows.cs | 1 - .../VirtualDriveSymbolicLinks.Windows.cs | 2 +- 4 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/SymbolicLinks/BaseSymbolicLinks.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/SymbolicLinks/BaseSymbolicLinks.cs index adaec2b9094f00..600cd987ec5d56 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/SymbolicLinks/BaseSymbolicLinks.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/SymbolicLinks/BaseSymbolicLinks.cs @@ -8,13 +8,9 @@ namespace System.IO.Tests { // Contains helper methods that are shared by all symbolic link test classes. + [ConditionalClass(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] public abstract partial class BaseSymbolicLinks : FileSystemTest { - public BaseSymbolicLinks() - { - Assert.True(MountHelper.CanCreateSymbolicLinks); - } - protected DirectoryInfo CreateDirectoryContainingSelfReferencingSymbolicLink() { DirectoryInfo testDirectory = Directory.CreateDirectory(GetRandomDirPath()); diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Enumeration/SymbolicLinksTests.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Enumeration/SymbolicLinksTests.cs index 48603c927d1841..3740d1f080a9e5 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Enumeration/SymbolicLinksTests.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Enumeration/SymbolicLinksTests.cs @@ -8,7 +8,6 @@ namespace System.IO.Tests.Enumeration { - [ConditionalClass(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] public class Enumeration_SymbolicLinksTests : BaseSymbolicLinks { [Fact] diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Junctions.Windows.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Junctions.Windows.cs index da5299946f44b2..c280008148977a 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Junctions.Windows.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Junctions.Windows.cs @@ -6,7 +6,6 @@ namespace System.IO.Tests { [PlatformSpecific(TestPlatforms.Windows)] - [ConditionalClass(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] public class Junctions : BaseSymbolicLinks { private DirectoryInfo CreateJunction(string junctionPath, string targetPath) diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/VirtualDriveSymbolicLinks.Windows.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/VirtualDriveSymbolicLinks.Windows.cs index 00242d1089f73d..a85754b2b3226e 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/VirtualDriveSymbolicLinks.Windows.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/VirtualDriveSymbolicLinks.Windows.cs @@ -8,7 +8,7 @@ namespace System.IO.Tests // Need to reuse the same virtual drive for all the test methods. // Creating and disposing one virtual drive per class achieves this. [PlatformSpecific(TestPlatforms.Windows)] - [ConditionalClass(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks), nameof(MountHelper.IsSubstAvailable))] + [ConditionalClass(typeof(MountHelper), nameof(MountHelper.IsSubstAvailable))] public class VirtualDrive_SymbolicLinks : BaseSymbolicLinks { private VirtualDriveHelper VirtualDrive { get; } = new VirtualDriveHelper(); From baf0ba207e31833a1af09f14fd1cc1e7687f9838 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Tue, 21 Oct 2025 00:50:42 +0800 Subject: [PATCH 32/37] Apply the same style change for the symlink --- src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs b/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs index d09c9b9a7568f0..bfd24ac0c13dae 100644 --- a/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs +++ b/src/libraries/Common/tests/System/IO/ReparsePointUtilities.cs @@ -161,7 +161,7 @@ public static bool CreateSymbolicLink(string linkPath, string targetPath, bool i symLinkProcess.WaitForExit(); - return (symLinkProcess.ExitCode == 0); + return symLinkProcess.ExitCode == 0; } private static bool IsProcessStartSupported() From 48cff98a9a918390c6f8b2953644afb88d3109f0 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Tue, 21 Oct 2025 04:10:55 +0800 Subject: [PATCH 33/37] Revert some attribute changes --- .../Base/HardLinks/BaseHardLinks.FileSystem.cs | 5 +++++ .../Base/SymbolicLinks/BaseSymbolicLinks.cs | 5 +++++ .../VirtualDriveSymbolicLinks.Windows.cs | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs index 344379018ee691..b77a0bc19a062b 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs @@ -11,6 +11,11 @@ namespace System.IO.Tests [ConditionalClass(typeof(MountHelper), nameof(MountHelper.CanCreateHardLinks))] public abstract class BaseHardLinks_FileSystem : FileSystemTest { + public BaseHardLinks_FileSystem() + { + Assert.True(MountHelper.CanCreateHardLinks); + } + /// Creates a new file depending on the implementing class. protected abstract void CreateFile(string path); diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/SymbolicLinks/BaseSymbolicLinks.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/SymbolicLinks/BaseSymbolicLinks.cs index 600cd987ec5d56..48222d38e03053 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/SymbolicLinks/BaseSymbolicLinks.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/SymbolicLinks/BaseSymbolicLinks.cs @@ -11,6 +11,11 @@ namespace System.IO.Tests [ConditionalClass(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))] public abstract partial class BaseSymbolicLinks : FileSystemTest { + public BaseSymbolicLinks() + { + Assert.True(MountHelper.CanCreateSymbolicLinks); + } + protected DirectoryInfo CreateDirectoryContainingSelfReferencingSymbolicLink() { DirectoryInfo testDirectory = Directory.CreateDirectory(GetRandomDirPath()); diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/VirtualDriveSymbolicLinks.Windows.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/VirtualDriveSymbolicLinks.Windows.cs index a85754b2b3226e..00242d1089f73d 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/VirtualDriveSymbolicLinks.Windows.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/VirtualDriveSymbolicLinks.Windows.cs @@ -8,7 +8,7 @@ namespace System.IO.Tests // Need to reuse the same virtual drive for all the test methods. // Creating and disposing one virtual drive per class achieves this. [PlatformSpecific(TestPlatforms.Windows)] - [ConditionalClass(typeof(MountHelper), nameof(MountHelper.IsSubstAvailable))] + [ConditionalClass(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks), nameof(MountHelper.IsSubstAvailable))] public class VirtualDrive_SymbolicLinks : BaseSymbolicLinks { private VirtualDriveHelper VirtualDrive { get; } = new VirtualDriveHelper(); From c14ad79593fc72c09002b388335e8aa5c22b31cc Mon Sep 17 00:00:00 2001 From: SadPencil Date: Tue, 21 Oct 2025 13:32:55 +0800 Subject: [PATCH 34/37] Revert the unnecessary file clean up for the hard link tests --- .../HardLinks/BaseHardLinks.FileSystem.cs | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs index b77a0bc19a062b..e15e70e36b06e2 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs @@ -43,11 +43,7 @@ public void CreateHardLink_TargetDoesNotExist_Throws() { string linkPath = GetRandomFilePath(); string nonExistentTarget = GetRandomFilePath(); - Assert.Throws(() => - { - CreateHardLink(linkPath, nonExistentTarget); - try { File.Delete(linkPath); } catch { } - }); + Assert.Throws(() => CreateHardLink(linkPath, nonExistentTarget)); } [Fact] @@ -59,13 +55,7 @@ public void CreateHardLink_LinkPathAlreadyExists_Throws() CreateFile(targetPath); CreateFile(linkPath); - Assert.Throws(() => - { - CreateHardLink(linkPath, targetPath); - try { File.Delete(linkPath); } catch { } - }); - - try { File.Delete(targetPath); } catch { } + Assert.Throws(() => CreateHardLink(linkPath, targetPath)); } [Fact] @@ -81,9 +71,6 @@ public void CreateHardLink_TargetExists_Succeeds() // Both files should have the same content Assert.Equal(File.ReadAllText(targetPath), File.ReadAllText(linkPath)); - - try { File.Delete(linkPath); } catch { } - try { File.Delete(targetPath); } catch { } } [Fact] @@ -101,8 +88,6 @@ public void CreateHardLink_ModifyViaOneLink_VisibleViaOther() // Read via target Assert.Equal("changed", File.ReadAllText(targetPath)); - try { File.Delete(linkPath); } catch { } - try { File.Delete(targetPath); } catch { } } [Fact] @@ -119,8 +104,6 @@ public void CreateHardLink_DeleteOneLink_FileStillAccessible() // The link should still exist and have the data Assert.Equal("data", File.ReadAllText(linkPath)); - - try { File.Delete(linkPath); } catch { } } } } From fc49998bd957e8dce363638138dc0c073bdd2c6a Mon Sep 17 00:00:00 2001 From: SadPencil Date: Tue, 21 Oct 2025 13:40:20 +0800 Subject: [PATCH 35/37] Add FileNotFoundException to the XML document of hard link methods --- src/libraries/System.Private.CoreLib/src/System/IO/File.cs | 1 + src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index f9e6ee4b0d8b65..9ae27137e96079 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -1420,6 +1420,7 @@ public static Task AppendAllLinesAsync(string path, IEnumerable contents /// or is empty. /// -or- /// or contains a null character. + /// The file specified by does not exist. /// A file or directory already exists in the location of . /// -or- /// An I/O error occurred. diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.cs index a32afc2215a1fc..b11b5ab5b80571 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.cs @@ -215,6 +215,7 @@ private StreamWriter CreateStreamWriter(bool append) /// This instance was not created passing an absolute path. /// -or- /// contains invalid path characters. + /// The file specified by does not exist. /// A file or directory already exists in the location of . /// -or- /// An I/O error occurred. From 56cfa9353d72066ee27be2496682d5fbcbe1a1d5 Mon Sep 17 00:00:00 2001 From: SadPencil Date: Wed, 22 Oct 2025 00:14:01 +0800 Subject: [PATCH 36/37] Add test CreateHardLink_RelativePath() --- .../Base/HardLinks/BaseHardLinks.FileSystem.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs index e15e70e36b06e2..ab1cae77d2a89b 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Base/HardLinks/BaseHardLinks.FileSystem.cs @@ -38,6 +38,16 @@ public void CreateHardLink_InvalidPathToTarget(string pathToTarget) Assert.Throws(() => CreateHardLink(GetRandomFilePath(), pathToTarget)); } + [Fact] + public void CreateHardLink_RelativePath() + { + string relativePathToTarget = GetRandomFileName(); + string relativePath = GetRandomFileName(); + + CreateFile(relativePathToTarget); + CreateHardLink(relativePath, relativePathToTarget); + } + [Fact] public void CreateHardLink_TargetDoesNotExist_Throws() { From 44c1e26f37b059f55572a8d8f62ae625527e021e Mon Sep 17 00:00:00 2001 From: SadPencil Date: Wed, 22 Oct 2025 00:15:47 +0800 Subject: [PATCH 37/37] Remove incorrect XML doc for CreateAsHardLink --- src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.cs index b11b5ab5b80571..199e702875af10 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileInfo.cs @@ -212,8 +212,6 @@ private StreamWriter CreateStreamWriter(bool append) /// is . /// is empty. /// -or- - /// This instance was not created passing an absolute path. - /// -or- /// contains invalid path characters. /// The file specified by does not exist. /// A file or directory already exists in the location of .