From 8e7069ae42c03337efd630c5a14e566ea05afd36 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Mon, 23 Aug 2021 21:00:00 -0700 Subject: [PATCH 01/19] Add mount point support to link APIs. --- .../Interop.GetFinalPathNameByHandle.cs | 2 + .../Kernel32/Interop.REPARSE_DATA_BUFFER.cs | 29 ++++++----- .../src/System/IO/FileSystem.Windows.cs | 49 ++++++++++++------- 3 files changed, 50 insertions(+), 30 deletions(-) diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetFinalPathNameByHandle.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetFinalPathNameByHandle.cs index 756b1bbd72db12..5e01c10fa5ed73 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetFinalPathNameByHandle.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetFinalPathNameByHandle.cs @@ -10,7 +10,9 @@ internal static partial class Interop { internal static partial class Kernel32 { + // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/fltkernel/ns-fltkernel-_flt_file_name_information#remarks internal const uint FILE_NAME_NORMALIZED = 0x0; + internal const uint FILE_NAME_OPENED = 0x8; // https://docs.microsoft.com/windows/desktop/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew (kernel32) [DllImport(Libraries.Kernel32, EntryPoint = "GetFinalPathNameByHandleW", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)] diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.REPARSE_DATA_BUFFER.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.REPARSE_DATA_BUFFER.cs index 3bcb9162d57bfc..123ac9235b9fdc 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.REPARSE_DATA_BUFFER.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.REPARSE_DATA_BUFFER.cs @@ -14,24 +14,29 @@ internal static partial class Kernel32 internal const uint SYMLINK_FLAG_RELATIVE = 1; // https://msdn.microsoft.com/library/windows/hardware/ff552012.aspx - // We don't need all the struct fields; omitting the rest. [StructLayout(LayoutKind.Sequential)] - internal unsafe struct REPARSE_DATA_BUFFER + internal unsafe struct SymbolicLinkReparseBuffer { internal uint ReparseTag; internal ushort ReparseDataLength; internal ushort Reserved; - internal SymbolicLinkReparseBuffer ReparseBufferSymbolicLink; + internal ushort SubstituteNameOffset; + internal ushort SubstituteNameLength; + internal ushort PrintNameOffset; + internal ushort PrintNameLength; + internal uint Flags; + } - [StructLayout(LayoutKind.Sequential)] - internal struct SymbolicLinkReparseBuffer - { - internal ushort SubstituteNameOffset; - internal ushort SubstituteNameLength; - internal ushort PrintNameOffset; - internal ushort PrintNameLength; - internal uint Flags; - } + [StructLayout(LayoutKind.Sequential)] + internal struct MountPointReparseBuffer + { + public uint ReparseTag; + public ushort ReparseDataLength; + public ushort Reserved; + public ushort SubstituteNameOffset; + public ushort SubstituteNameLength; + public ushort PrintNameOffset; + public ushort PrintNameLength; } } } 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 b92de0ad38ae9b..7c09a4954cab7a 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 @@ -499,32 +499,45 @@ internal static void CreateSymbolicLink(string path, string pathToTarget, bool i } Span bufferSpan = new(buffer); - success = MemoryMarshal.TryRead(bufferSpan, out Interop.Kernel32.REPARSE_DATA_BUFFER rdb); + success = MemoryMarshal.TryRead(bufferSpan, out Interop.Kernel32.SymbolicLinkReparseBuffer rbSymlink); Debug.Assert(success); - // Only symbolic links are supported at the moment. - if ((rdb.ReparseTag & Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_SYMLINK) == 0) + if (rbSymlink.ReparseTag == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_SYMLINK) { - return null; - } + // We use PrintName instead of SubstituteName given that we don't want to return a NT path when the link wasn't created with such NT path. + // Unlike SubstituteName and GetFinalPathNameByHandle(), PrintName doesn't start with a prefix. + // Another nuance is that SubstituteName does not contain redundant path segments while PrintName does. + // PrintName can ONLY return a NT path if the link was created explicitly targeting a file/folder in such way. e.g: mklink /D linkName \??\C:\path\to\target. - // We use PrintName instead of SubstitutneName given that we don't want to return a NT path when the link wasn't created with such NT path. - // Unlike SubstituteName and GetFinalPathNameByHandle(), PrintName doesn't start with a prefix. - // Another nuance is that SubstituteName does not contain redundant path segments while PrintName does. - // PrintName can ONLY return a NT path if the link was created explicitly targeting a file/folder in such way. e.g: mklink /D linkName \??\C:\path\to\target. - int printNameNameOffset = sizeof(Interop.Kernel32.REPARSE_DATA_BUFFER) + rdb.ReparseBufferSymbolicLink.PrintNameOffset; - int printNameNameLength = rdb.ReparseBufferSymbolicLink.PrintNameLength; + int printNameNameOffset = sizeof(Interop.Kernel32.SymbolicLinkReparseBuffer) + rbSymlink.PrintNameOffset; + int printNameNameLength = rbSymlink.PrintNameLength; - Span targetPath = MemoryMarshal.Cast(bufferSpan.Slice(printNameNameOffset, printNameNameLength)); - Debug.Assert((rdb.ReparseBufferSymbolicLink.Flags & Interop.Kernel32.SYMLINK_FLAG_RELATIVE) == 0 || !PathInternal.IsExtended(targetPath)); + Span targetPathSymlink = MemoryMarshal.Cast(bufferSpan.Slice(printNameNameOffset, printNameNameLength)); + Debug.Assert((rbSymlink.Flags & Interop.Kernel32.SYMLINK_FLAG_RELATIVE) == 0 || !PathInternal.IsExtended(targetPathSymlink)); + + if (returnFullPath && (rbSymlink.Flags & Interop.Kernel32.SYMLINK_FLAG_RELATIVE) != 0) + { + // Target path is relative and is for ResolveLinkTarget(), we need to append the link directory. + return Path.Join(Path.GetDirectoryName(linkPath.AsSpan()), targetPathSymlink); + } - if (returnFullPath && (rdb.ReparseBufferSymbolicLink.Flags & Interop.Kernel32.SYMLINK_FLAG_RELATIVE) != 0) + return targetPathSymlink.ToString(); + } + else if (rbSymlink.ReparseTag == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT) { - // Target path is relative and is for ResolveLinkTarget(), we need to append the link directory. - return Path.Join(Path.GetDirectoryName(linkPath.AsSpan()), targetPath); + success = MemoryMarshal.TryRead(bufferSpan, out Interop.Kernel32.MountPointReparseBuffer rbMountPoint); + Debug.Assert(success); + + int printNameNameOffset = sizeof(Interop.Kernel32.MountPointReparseBuffer) + rbMountPoint.PrintNameOffset; + int printNameNameLength = rbMountPoint.PrintNameLength; + + Span targetPathMountPoint = MemoryMarshal.Cast(bufferSpan.Slice(printNameNameOffset, printNameNameLength)); + Debug.Assert(!PathInternal.IsExtended(targetPathMountPoint)); + + return targetPathMountPoint.ToString(); } - return targetPath.ToString(); + return null; } finally { @@ -602,7 +615,7 @@ uint GetFinalPathNameByHandle(SafeFileHandle handle, char[] buffer) { fixed (char* bufPtr = buffer) { - return Interop.Kernel32.GetFinalPathNameByHandle(handle, bufPtr, (uint)buffer.Length, Interop.Kernel32.FILE_NAME_NORMALIZED); + return Interop.Kernel32.GetFinalPathNameByHandle(handle, bufPtr, (uint)buffer.Length, Interop.Kernel32.FILE_NAME_OPENED); } } From 11960b42fee692611616e496aa6df118f11b0532 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Mon, 23 Aug 2021 21:00:16 -0700 Subject: [PATCH 02/19] Add junction and virtual drive tests. --- .../SymbolicLinks/BaseJunctions.FileSystem.cs | 81 +++++ .../BaseSymbolicLinks.FileSystem.cs | 5 +- .../Base/SymbolicLinks/BaseSymbolicLinks.cs | 22 +- .../tests/Directory/Junctions.cs | 43 +++ .../tests/DirectoryInfo/Junctions.cs | 51 +++ .../PortedCommon/ReparsePointUtilities.cs | 96 +++++- .../tests/System.IO.FileSystem.Tests.csproj | 9 +- .../VirtualDriveSymbolicLinks.Windows.cs | 291 ++++++++++++++++++ 8 files changed, 573 insertions(+), 25 deletions(-) create mode 100644 src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs create mode 100644 src/libraries/System.IO.FileSystem/tests/Directory/Junctions.cs create mode 100644 src/libraries/System.IO.FileSystem/tests/DirectoryInfo/Junctions.cs create mode 100644 src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs diff --git a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs new file mode 100644 index 00000000000000..d16bd3d6fad678 --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace System.IO.Tests +{ + public abstract class BaseJunctions_FileSystem : BaseSymbolicLinks + { + protected DirectoryInfo CreateJunction(string junctionPath, string targetPath) + { + Assert.True(MountHelper.CreateJunction(junctionPath, targetPath)); + DirectoryInfo junctionInfo = new(junctionPath); + return junctionInfo; + } + + protected abstract DirectoryInfo CreateDirectory(string path); + + protected abstract FileSystemInfo ResolveLinkTarget(string junctionPath, bool returnFinalTarget); + + protected abstract void VerifyEnumerateMethods(string junctionPath, string[] expectedFiles, string[] expectedDirectories, string[] expectedEntries); + + protected void VerifyEnumeration(IEnumerable expectedEnumeration, IEnumerable actualEnumeration) + { + foreach (string expectedItem in expectedEnumeration) + { + Assert.True(actualEnumeration.Contains(expectedItem)); + } + } + + [Fact] + public void Junction_ResolveLinkTarget() + { + string junctionPath = GetRandomLinkPath(); + string targetPath = GetRandomDirPath(); + + CreateDirectory(targetPath); + DirectoryInfo junctionInfo = CreateJunction(junctionPath, targetPath); + + FileSystemInfo? actualTargetInfo = ResolveLinkTarget(junctionPath, returnFinalTarget: false); + Assert.True(actualTargetInfo is DirectoryInfo); + Assert.Equal(targetPath, actualTargetInfo.FullName); + Assert.Equal(targetPath, junctionInfo.LinkTarget); + } + + [Fact] + public void Junction_EnumerateFileSystemEntries() + { + // Root + string targetPath = GetRandomDirPath(); + Directory.CreateDirectory(targetPath); + + string fileName = GetRandomFileName(); + string subDirName = GetRandomDirName(); + string subFileName = Path.Join(subDirName, GetRandomFileName()); + + string filePath = Path.Join(targetPath, fileName); + string subDirPath = Path.Join(targetPath, subDirName); + string subFilePath = Path.Join(targetPath, subFileName); + + File.Create(filePath).Dispose(); + Directory.CreateDirectory(subDirPath); + File.Create(subFilePath).Dispose(); + + string junctionPath = GetRandomLinkPath(); + CreateJunction(junctionPath, targetPath); + + string jFilePath = Path.Join(junctionPath, fileName); + string jSubDirPath = Path.Join(junctionPath, subDirName); + string jSubFilePath = Path.Join(junctionPath, subFileName); + + string[] expectedFiles = new[] { jFilePath, jSubFilePath }; + string[] expectedDirectories = new[] { jSubDirPath }; + string[] expectedEntries = new[] { jFilePath, jSubDirPath, jSubFilePath }; + + VerifyEnumerateMethods(junctionPath, expectedFiles, expectedDirectories, expectedEntries); + } + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs index 0682f8ce16e5bb..e4b70523c37fb7 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs @@ -460,11 +460,10 @@ private void ResolveLinkTarget_ReturnFinalTarget(string link1Path, string link1T Assert.Equal(filePath, finalTarget.FullName); } + // Must call inside a remote executor protected void CreateSymbolicLink_PathToTarget_RelativeToLinkPath_Internal(bool createOpposite) { - string tempCwd = GetRandomDirPath(); - Directory.CreateDirectory(tempCwd); - Directory.SetCurrentDirectory(tempCwd); + string tempCwd = GetNewCwdPath(); // Create a dummy file or directory in cwd. string fileOrDirectoryInCwd = GetRandomFileName(); diff --git a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.cs b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.cs index f120cdf10eba53..cb5b6a48497801 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.cs @@ -1,10 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Buffers; -using System.Diagnostics; -using System.Runtime.InteropServices; -using Microsoft.Win32.SafeHandles; using Xunit; namespace System.IO.Tests @@ -29,12 +25,26 @@ protected DirectoryInfo CreateSelfReferencingSymbolicLink() protected string GetRandomFileName() => GetTestFileName() + ".txt"; protected string GetRandomLinkName() => GetTestFileName() + ".link"; - protected string GetRandomDirName() => GetTestFileName() + "_dir"; + protected string GetRandomDirName() => GetTestFileName() + "_dir"; protected string GetRandomFilePath() => Path.Join(ActualTestDirectory.Value, GetRandomFileName()); protected string GetRandomLinkPath() => Path.Join(ActualTestDirectory.Value, GetRandomLinkName()); - protected string GetRandomDirPath() => Path.Join(ActualTestDirectory.Value, GetRandomDirName()); + protected string GetRandomDirPath() => Path.Join(ActualTestDirectory.Value, GetRandomDirName()); private Lazy ActualTestDirectory => new Lazy(() => GetTestDirectoryActualCasing()); + + /// + /// Changes the current working directory path to a new temporary directory. + /// Important: Make sure to call this inside a remote executor to avoid changing the cwd for all tests in same process. + /// + /// The path of the new cwd. + protected string GetNewCwdPath() + { + string tempCwd = GetRandomDirPath(); + Directory.CreateDirectory(tempCwd); + Directory.SetCurrentDirectory(tempCwd); + return tempCwd; + } + } } diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/Junctions.cs b/src/libraries/System.IO.FileSystem/tests/Directory/Junctions.cs new file mode 100644 index 00000000000000..d3f779c3f91f20 --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/Directory/Junctions.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.IO.Tests +{ + public class Directory_Junctions : BaseJunctions_FileSystem + { + protected override DirectoryInfo CreateDirectory(string path) => + Directory.CreateDirectory(path); + + protected override FileSystemInfo? ResolveLinkTarget(string junctionPath, bool returnFinalTarget) => + Directory.ResolveLinkTarget(junctionPath, returnFinalTarget); + + protected override void VerifyEnumerateMethods(string junctionPath, string[] expectedFiles, string[] expectedDirectories, string[] expectedEntries) + { + EnumerationOptions options = new() { RecurseSubdirectories = true }; + + VerifyEnumeration( + Directory.EnumerateFiles(junctionPath, "*", options), + expectedFiles); + + VerifyEnumeration( + Directory.EnumerateDirectories(junctionPath, "*", options), + expectedDirectories); + + VerifyEnumeration( + Directory.EnumerateFileSystemEntries(junctionPath, "*", options), + expectedEntries); + + VerifyEnumeration( + Directory.GetFiles(junctionPath, "*", options), + expectedFiles); + + VerifyEnumeration( + Directory.GetDirectories(junctionPath, "*", options), + expectedDirectories); + + VerifyEnumeration( + Directory.GetFileSystemEntries(junctionPath, "*", options), + expectedEntries); + } + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/Junctions.cs b/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/Junctions.cs new file mode 100644 index 00000000000000..8d71513a446c8d --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/Junctions.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Linq; + +namespace System.IO.Tests +{ + public class DirectoryInfo_Junctions : BaseJunctions_FileSystem + { + protected override DirectoryInfo CreateDirectory(string path) + { + DirectoryInfo dirInfo = new(path); + dirInfo.Create(); + return dirInfo; + } + + protected override FileSystemInfo? ResolveLinkTarget(string junctionPath, bool returnFinalTarget) => + new DirectoryInfo(junctionPath).ResolveLinkTarget(returnFinalTarget); + + protected override void VerifyEnumerateMethods(string junctionPath, string[] expectedFiles, string[] expectedDirectories, string[] expectedEntries) + { + EnumerationOptions options = new() { RecurseSubdirectories = true }; + + DirectoryInfo info = new(junctionPath); + + VerifyEnumeration( + info.EnumerateFiles("*", options).Select(x => x.FullName), + expectedFiles); + + VerifyEnumeration( + info.EnumerateDirectories("*", options).Select(x => x.FullName), + expectedDirectories); + + VerifyEnumeration( + info.EnumerateFileSystemInfos("*", options).Select(x => x.FullName), + expectedEntries); + + VerifyEnumeration( + info.GetFiles("*", options).Select(x => x.FullName), + expectedFiles); + + VerifyEnumeration( + info.GetDirectories("*", options).Select(x => x.FullName), + expectedDirectories); + + VerifyEnumeration( + info.GetFileSystemInfos("*", options).Select(x => x.FullName), + expectedEntries); + } + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs b/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs index 5423bda6814b3b..182fa35c1b8263 100644 --- a/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs +++ b/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs @@ -10,14 +10,14 @@ #define DEBUG using System; -using System.IO; -using System.Text; -using System.Diagnostics; using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; using System.Runtime.InteropServices; -using System.ComponentModel; -using System.Threading; +using System.Text; using System.Threading.Tasks; + public static class MountHelper { [DllImport("kernel32.dll", EntryPoint = "GetVolumeNameForVolumeMountPointW", CharSet = CharSet.Unicode, BestFitMapping = false, SetLastError = true)] @@ -37,25 +37,95 @@ public static bool CreateSymbolicLink(string linkPath, string targetPath, bool i if (OperatingSystem.IsWindows()) { symLinkProcess.StartInfo.FileName = "cmd"; - symLinkProcess.StartInfo.Arguments = string.Format("/c mklink{0} \"{1}\" \"{2}\"", isDirectory ? " /D" : "", linkPath, targetPath); + string option = isDirectory ? "/D" : ""; + symLinkProcess.StartInfo.Arguments = $"/c mklink {option} \"{linkPath}\" \"{targetPath}\""; } else { symLinkProcess.StartInfo.FileName = "/bin/ln"; - symLinkProcess.StartInfo.Arguments = string.Format("-s \"{0}\" \"{1}\"", targetPath, linkPath); + symLinkProcess.StartInfo.Arguments = $"-s \"{targetPath}\" \"{linkPath}\""; } symLinkProcess.StartInfo.UseShellExecute = false; symLinkProcess.StartInfo.RedirectStandardOutput = true; symLinkProcess.Start(); + symLinkProcess.WaitForExit(); + return (0 == symLinkProcess.ExitCode); + } - if (symLinkProcess != null) + public static bool CreateJunction(string junctionPath, string targetPath) + { + if (!OperatingSystem.IsWindows()) { - symLinkProcess.WaitForExit(); - return (0 == symLinkProcess.ExitCode); + throw new PlatformNotSupportedException(); } - else + + Process junctionProcess = new Process(); + junctionProcess.StartInfo.FileName = "cmd"; + junctionProcess.StartInfo.Arguments = $"/c mklink /J \"{junctionPath}\" \"{targetPath}\""; + junctionProcess.StartInfo.UseShellExecute = false; + junctionProcess.StartInfo.RedirectStandardOutput = true; + junctionProcess.Start(); + junctionProcess.WaitForExit(); + return (0 == junctionProcess.ExitCode); + } + + public static char CreateVirtualDrive(string targetDir) + { + if (!PlatformDetection.IsWindows) + { + throw new PlatformNotSupportedException(); + } + + char driveLetter = GetRandomDriveLetter(); + + Process substProcess = new Process(); + substProcess.StartInfo.FileName = "subst"; + substProcess.StartInfo.Arguments = $"{driveLetter}: {targetDir}"; + substProcess.StartInfo.UseShellExecute = false; + substProcess.StartInfo.RedirectStandardOutput = true; + substProcess.Start(); + substProcess.WaitForExit(); + if (!DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter)) + { + throw new InvalidOperationException($"Could not create virtual drive {driveLetter}: with subst"); + } + return driveLetter; + + char GetRandomDriveLetter() + { + List existingDrives = DriveInfo.GetDrives().Select(x => x.Name[0]).ToList(); + + // A,B are reserved, C is usually reserved + IEnumerable range = Enumerable.Range('D', 'Z' - 'D'); + IEnumerable castRange = range.Select(x => Convert.ToChar(x)); + IEnumerable allDrivesLetters = castRange.Except(existingDrives); + + if (!allDrivesLetters.Any()) + { + throw new ArgumentOutOfRangeException("No drive letters available"); + } + + return allDrivesLetters.First(); + } + } + + public static void DeleteVirtualDrive(char driveLetter) + { + if (!PlatformDetection.IsWindows) + { + throw new PlatformNotSupportedException(); + } + + Process substProcess = new Process(); + substProcess.StartInfo.FileName = "subst"; + substProcess.StartInfo.Arguments = $"/d {driveLetter}:"; + substProcess.StartInfo.UseShellExecute = false; + substProcess.StartInfo.RedirectStandardOutput = true; + substProcess.Start(); + substProcess.WaitForExit(); + if (DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter)) { - return false; + throw new InvalidOperationException($"Could not delete virtual drive {driveLetter}: with subst"); } } @@ -94,7 +164,7 @@ public static void Unmount(string mountPoint) } /// For standalone debugging help. Change Main0 to Main - public static void Main0(string[] args) + public static void Main0(string[] args) { try { diff --git a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj index 736d3b1ee5654b..8f467d0ed2a5ba 100644 --- a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj +++ b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj @@ -1,4 +1,4 @@ - + true true @@ -75,14 +75,18 @@ + + + + @@ -211,8 +215,7 @@ - + diff --git a/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs b/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs new file mode 100644 index 00000000000000..2fcb621f51ca0c --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs @@ -0,0 +1,291 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +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. + public class VirtualDrive_SymbolicLinks : BaseSymbolicLinks + { + protected override void Dispose(bool disposing) + { + try + { + if (VirtualDriveLetter != default) + { + MountHelper.DeleteVirtualDrive(VirtualDriveLetter); + Directory.Delete(VirtualDriveTargetDir, recursive: true); + } + } + catch { } // avoid exceptions on dispose + base.Dispose(disposing); + } + + // When the immediate target is requested (via LinkTarget or using returnFinalTarget: false), + // the target path will use the virtual drive + // The only case of returnFinalTarget: true that will return a target using the virtual drive, is when both + // the link and the target reside in the virtual drive + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsWindows))] + [InlineData(true, true, false)] + [InlineData(false, true, false)] + [InlineData(true, false, false)] + [InlineData(true, false, true)] + public void CreateSymbolicLinkVD_VirtualPathReturned(bool linkInVD, bool targetInVD, bool returnFinalTarget) + { + // File + CreateSymbolicLinkVD_VirtualPathReturned_Internal( + linkInVD, + targetInVD, + returnFinalTarget, + isDirectoryTest: false, + CreateFile, + CreateFileSymbolicLink, + AssertFileIsCorrectTypeAndDirectoryAttribute); + + // Directory + CreateSymbolicLinkVD_VirtualPathReturned_Internal( + linkInVD, + targetInVD, + returnFinalTarget, + isDirectoryTest: true, + CreateDirectory, + CreateDirectorySymbolicLink, + AssertDirectoryIsCorrectTypeAndDirectoryAttribute); + + // FileInfo + CreateSymbolicLinkVD_VirtualPathReturned_Internal( + linkInVD, + targetInVD, + returnFinalTarget, + isDirectoryTest: false, + CreateFile, + CreateFileInfoSymbolicLink, + AssertFileIsCorrectTypeAndDirectoryAttribute); + + // DirectoryInfo + CreateSymbolicLinkVD_VirtualPathReturned_Internal( + linkInVD, + targetInVD, + returnFinalTarget, + isDirectoryTest: true, + CreateDirectory, + CreateDirectoryInfoSymbolicLink, + AssertDirectoryIsCorrectTypeAndDirectoryAttribute); + } + + // When the link target resides in a virtual drive, and returnFinalTarget: true is used, + // All segments of the target path get resolved, including mount points, to their real path + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsWindows))] + [InlineData(false)] + [InlineData(true)] + public void CreateSymbolicLinkVD_ResolvedPathReturned(bool linkInVD) + { + // File + CreateSymbolicLinkVD_ResolvedPathReturned_Internal( + linkInVD, + targetInVD: true, + returnFinalTarget: true, + isDirectoryTest: false, + CreateFile, + CreateFileSymbolicLink, + AssertFileIsCorrectTypeAndDirectoryAttribute); + + //.Directory + CreateSymbolicLinkVD_ResolvedPathReturned_Internal( + linkInVD, + targetInVD: true, + returnFinalTarget: true, + isDirectoryTest: true, + CreateDirectory, + CreateDirectorySymbolicLink, + AssertDirectoryIsCorrectTypeAndDirectoryAttribute); + + // FileInfo + CreateSymbolicLinkVD_ResolvedPathReturned_Internal( + linkInVD, + targetInVD: true, + returnFinalTarget: true, + isDirectoryTest: false, + CreateFile, + CreateFileInfoSymbolicLink, + AssertFileIsCorrectTypeAndDirectoryAttribute); + + //.DirectoryInfo + CreateSymbolicLinkVD_ResolvedPathReturned_Internal( + linkInVD, + targetInVD: true, + returnFinalTarget: true, + isDirectoryTest: true, + CreateDirectory, + CreateDirectoryInfoSymbolicLink, + AssertDirectoryIsCorrectTypeAndDirectoryAttribute); + } + + private void CreateSymbolicLinkVD_VirtualPathReturned_Internal( + bool linkInVD, + bool targetInVD, + bool returnFinalTarget, + bool isDirectoryTest, + TargetCreationMethod targetCreationMethod, + SymbolicLinkCreationMethod symbolicLinkCreationMethod, + AssertIsCorrectTypeAndDirectoryAttributeMethod assertIsCorrectTypeAndDirectoryAttributeMethod) + { + (string targetPath, FileSystemInfo linkInfo, FileSystemInfo targetInfo) = + CreateSymbolicLinkVD_Internal( + linkInVD, + targetInVD, + returnFinalTarget, + isDirectoryTest, + targetCreationMethod, + symbolicLinkCreationMethod, + assertIsCorrectTypeAndDirectoryAttributeMethod); + + Assert.Equal(targetPath, linkInfo.LinkTarget); + Assert.Equal(targetPath, targetInfo.FullName); + } + + private void CreateSymbolicLinkVD_ResolvedPathReturned_Internal( + bool linkInVD, + bool targetInVD, + bool returnFinalTarget, + bool isDirectoryTest, + TargetCreationMethod targetCreationMethod, + SymbolicLinkCreationMethod symbolicLinkCreationMethod, + AssertIsCorrectTypeAndDirectoryAttributeMethod assertIsCorrectTypeAndDirectoryAttributeMethod) + { + (string targetPath, FileSystemInfo linkInfo, FileSystemInfo targetInfo) = + CreateSymbolicLinkVD_Internal( + linkInVD, + targetInVD, + returnFinalTarget, + isDirectoryTest, + targetCreationMethod, + symbolicLinkCreationMethod, + assertIsCorrectTypeAndDirectoryAttributeMethod); + + Assert.Equal(targetPath, linkInfo.LinkTarget); + string resolvedTargetPath = Path.Join(VirtualDriveTargetDir, Path.GetFileName(targetPath)); + Assert.Equal(resolvedTargetPath, targetInfo.FullName); + } + + private (string, FileSystemInfo, FileSystemInfo) CreateSymbolicLinkVD_Internal( + bool linkInVD, + bool targetInVD, + bool returnFinalTarget, + bool isDirectoryTest, + TargetCreationMethod createFileOrDirectory, + SymbolicLinkCreationMethod createSymbolicLink, + AssertIsCorrectTypeAndDirectoryAttributeMethod assertIsCorrectTypeAndDirectoryAttribute) + { + string targetFileName = isDirectoryTest ? GetRandomDirName() : GetRandomFileName(); + string pathToTarget = Path.Join( + targetInVD ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, + targetFileName); + + string linkFileName = GetRandomLinkName(); + string linkPath = Path.Join( + linkInVD ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, + linkFileName); + + createFileOrDirectory(pathToTarget); + + FileSystemInfo? link = createSymbolicLink(linkPath, pathToTarget); + assertIsCorrectTypeAndDirectoryAttribute(link); + + FileSystemInfo? target = link.ResolveLinkTarget(returnFinalTarget); + assertIsCorrectTypeAndDirectoryAttribute(target); + + return (pathToTarget, link, target); + } + + private delegate void TargetCreationMethod(string targetPath); + + private void CreateFile(string targetPath) => File.Create(targetPath).Dispose(); + + private void CreateDirectory(string targetPath) => Directory.CreateDirectory(targetPath); + + private delegate FileSystemInfo SymbolicLinkCreationMethod(string path, string pathToTarget); + + private FileSystemInfo CreateFileSymbolicLink(string path, string pathToTarget) => File.CreateSymbolicLink(path, pathToTarget); + + private FileSystemInfo CreateDirectorySymbolicLink(string path, string pathToTarget) => Directory.CreateSymbolicLink(path, pathToTarget); + + private FileSystemInfo CreateFileInfoSymbolicLink(string path, string pathToTarget) + { + FileInfo link = new(path); + link.CreateAsSymbolicLink(pathToTarget); + return link; + } + + private FileSystemInfo CreateDirectoryInfoSymbolicLink(string path, string pathToTarget) + { + DirectoryInfo link = new(path); + link.CreateAsSymbolicLink(pathToTarget); + return link; + } + + private delegate void AssertIsCorrectTypeAndDirectoryAttributeMethod(FileSystemInfo link); + + private void AssertFileIsCorrectTypeAndDirectoryAttribute(FileSystemInfo link) + { + if (link.Exists) + { + Assert.False(link.Attributes.HasFlag(FileAttributes.Directory)); + } + Assert.True(link is FileInfo); + } + + private void AssertDirectoryIsCorrectTypeAndDirectoryAttribute(FileSystemInfo link) + { + if (link.Exists) + { + Assert.True(link.Attributes.HasFlag(FileAttributes.Directory)); + } + Assert.True(link is DirectoryInfo); + } + + // Temporary Windows directory that can be mounted to a drive letter using the subst command + private string? _virtualDriveTargetDir = null; + private string VirtualDriveTargetDir + { + get + { + if (_virtualDriveTargetDir == null) + { + if (!PlatformDetection.IsWindows) + { + throw new PlatformNotSupportedException(); + } + + // Create a folder inside the temp directory so that it can be mounted to a drive letter with subst + _virtualDriveTargetDir = Path.Join(Path.GetTempPath(), GetRandomDirName()); + Directory.CreateDirectory(_virtualDriveTargetDir); + } + + return _virtualDriveTargetDir; + } + } + + // Windows drive letter that points to a mounted directory using the subst command + private char _virtualDriveLetter = default; + private char VirtualDriveLetter + { + get + { + if (_virtualDriveLetter == default) + { + if (!PlatformDetection.IsWindows) + { + throw new PlatformNotSupportedException(); + } + + // Mount the folder to a drive letter + _virtualDriveLetter = MountHelper.CreateVirtualDrive(VirtualDriveTargetDir); + } + return _virtualDriveLetter; + } + } + } +} From e59f2b236740001b6782ebf4768472837b79e4a1 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Tue, 24 Aug 2021 12:59:55 -0700 Subject: [PATCH 03/19] Move PrintName comment outside of if else of reparseTag check. --- .../Base/SymbolicLinks/BaseSymbolicLinks.cs | 4 +-- .../src/System/IO/FileSystem.Windows.cs | 27 ++++++++++--------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.cs b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.cs index cb5b6a48497801..1e5c7c4a3aa3e2 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.cs @@ -25,11 +25,11 @@ protected DirectoryInfo CreateSelfReferencingSymbolicLink() protected string GetRandomFileName() => GetTestFileName() + ".txt"; protected string GetRandomLinkName() => GetTestFileName() + ".link"; - protected string GetRandomDirName() => GetTestFileName() + "_dir"; + protected string GetRandomDirName() => GetTestFileName() + "_dir"; protected string GetRandomFilePath() => Path.Join(ActualTestDirectory.Value, GetRandomFileName()); protected string GetRandomLinkPath() => Path.Join(ActualTestDirectory.Value, GetRandomLinkName()); - protected string GetRandomDirPath() => Path.Join(ActualTestDirectory.Value, GetRandomDirName()); + protected string GetRandomDirPath() => Path.Join(ActualTestDirectory.Value, GetRandomDirName()); private Lazy ActualTestDirectory => new Lazy(() => GetTestDirectoryActualCasing()); 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 7c09a4954cab7a..8e17984e68f4ea 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 @@ -502,17 +502,19 @@ internal static void CreateSymbolicLink(string path, string pathToTarget, bool i success = MemoryMarshal.TryRead(bufferSpan, out Interop.Kernel32.SymbolicLinkReparseBuffer rbSymlink); Debug.Assert(success); + // We use PrintName(Offset|Length) instead of SubstituteName(Offset|Length) given that we don't want to return + // an NT path when the link wasn't created with such NT path. + // Unlike SubstituteName and GetFinalPathNameByHandle(), PrintName doesn't start with a prefix. + // Another nuance is that SubstituteName does not contain redundant path segments while PrintName does. + // PrintName can ONLY return a NT path if the link was created explicitly targeting a file/folder in such way. + // e.g: mklink /D linkName \??\C:\path\to\target. + if (rbSymlink.ReparseTag == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_SYMLINK) { - // We use PrintName instead of SubstituteName given that we don't want to return a NT path when the link wasn't created with such NT path. - // Unlike SubstituteName and GetFinalPathNameByHandle(), PrintName doesn't start with a prefix. - // Another nuance is that SubstituteName does not contain redundant path segments while PrintName does. - // PrintName can ONLY return a NT path if the link was created explicitly targeting a file/folder in such way. e.g: mklink /D linkName \??\C:\path\to\target. - - int printNameNameOffset = sizeof(Interop.Kernel32.SymbolicLinkReparseBuffer) + rbSymlink.PrintNameOffset; - int printNameNameLength = rbSymlink.PrintNameLength; + int printNameOffset = sizeof(Interop.Kernel32.SymbolicLinkReparseBuffer) + rbSymlink.PrintNameOffset; + int printNameLength = rbSymlink.PrintNameLength; - Span targetPathSymlink = MemoryMarshal.Cast(bufferSpan.Slice(printNameNameOffset, printNameNameLength)); + Span targetPathSymlink = MemoryMarshal.Cast(bufferSpan.Slice(printNameOffset, printNameLength)); Debug.Assert((rbSymlink.Flags & Interop.Kernel32.SYMLINK_FLAG_RELATIVE) == 0 || !PathInternal.IsExtended(targetPathSymlink)); if (returnFullPath && (rbSymlink.Flags & Interop.Kernel32.SYMLINK_FLAG_RELATIVE) != 0) @@ -528,12 +530,13 @@ internal static void CreateSymbolicLink(string path, string pathToTarget, bool i success = MemoryMarshal.TryRead(bufferSpan, out Interop.Kernel32.MountPointReparseBuffer rbMountPoint); Debug.Assert(success); - int printNameNameOffset = sizeof(Interop.Kernel32.MountPointReparseBuffer) + rbMountPoint.PrintNameOffset; - int printNameNameLength = rbMountPoint.PrintNameLength; + int printNameOffset = sizeof(Interop.Kernel32.MountPointReparseBuffer) + rbMountPoint.PrintNameOffset; + int printNameLength = rbMountPoint.PrintNameLength; - Span targetPathMountPoint = MemoryMarshal.Cast(bufferSpan.Slice(printNameNameOffset, printNameNameLength)); - Debug.Assert(!PathInternal.IsExtended(targetPathMountPoint)); + Span targetPathMountPoint = MemoryMarshal.Cast(bufferSpan.Slice(printNameOffset, printNameLength)); + // Unlink symlinks, mount point paths cannot be relative + Debug.Assert(!PathInternal.IsExtended(targetPathMountPoint)); return targetPathMountPoint.ToString(); } From 2923c9e22c772560e01c921b7eb14b1ad59485f6 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Tue, 24 Aug 2021 14:41:47 -0700 Subject: [PATCH 04/19] Add Windows platform specific attribute to junction and virtual drive test classes. --- .../tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs | 1 + .../System.IO.FileSystem/tests/Directory/Junctions.cs | 3 +++ .../System.IO.FileSystem/tests/DirectoryInfo/Junctions.cs | 2 ++ .../tests/VirtualDriveSymbolicLinks.Windows.cs | 5 +++-- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs index d16bd3d6fad678..79a42cd3a9d50d 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs @@ -7,6 +7,7 @@ namespace System.IO.Tests { + [PlatformSpecific(TestPlatforms.Windows)] public abstract class BaseJunctions_FileSystem : BaseSymbolicLinks { protected DirectoryInfo CreateJunction(string junctionPath, string targetPath) diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/Junctions.cs b/src/libraries/System.IO.FileSystem/tests/Directory/Junctions.cs index d3f779c3f91f20..3a999d283fcc6f 100644 --- a/src/libraries/System.IO.FileSystem/tests/Directory/Junctions.cs +++ b/src/libraries/System.IO.FileSystem/tests/Directory/Junctions.cs @@ -1,8 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Xunit; + namespace System.IO.Tests { + [PlatformSpecific(TestPlatforms.Windows)] public class Directory_Junctions : BaseJunctions_FileSystem { protected override DirectoryInfo CreateDirectory(string path) => diff --git a/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/Junctions.cs b/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/Junctions.cs index 8d71513a446c8d..2ebb0798473932 100644 --- a/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/Junctions.cs +++ b/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/Junctions.cs @@ -2,9 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Linq; +using Xunit; namespace System.IO.Tests { + [PlatformSpecific(TestPlatforms.Windows)] public class DirectoryInfo_Junctions : BaseJunctions_FileSystem { protected override DirectoryInfo CreateDirectory(string path) diff --git a/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs b/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs index 2fcb621f51ca0c..7d1a8dbb013d95 100644 --- a/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs +++ b/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs @@ -7,6 +7,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)] public class VirtualDrive_SymbolicLinks : BaseSymbolicLinks { protected override void Dispose(bool disposing) @@ -27,7 +28,7 @@ protected override void Dispose(bool disposing) // the target path will use the virtual drive // The only case of returnFinalTarget: true that will return a target using the virtual drive, is when both // the link and the target reside in the virtual drive - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsWindows))] + [Theory] [InlineData(true, true, false)] [InlineData(false, true, false)] [InlineData(true, false, false)] @@ -77,7 +78,7 @@ public void CreateSymbolicLinkVD_VirtualPathReturned(bool linkInVD, bool targetI // When the link target resides in a virtual drive, and returnFinalTarget: true is used, // All segments of the target path get resolved, including mount points, to their real path - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsWindows))] + [Theory] [InlineData(false)] [InlineData(true)] public void CreateSymbolicLinkVD_ResolvedPathReturned(bool linkInVD) From b6cf8579c3437fb84f8f85ea90d814d401cbab18 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Tue, 24 Aug 2021 14:55:42 -0700 Subject: [PATCH 05/19] Revert FILE_NAME_OPENED to FILE_NAME_NORMALIZED --- .../System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8e17984e68f4ea..455c03ecb19d62 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 @@ -618,7 +618,7 @@ uint GetFinalPathNameByHandle(SafeFileHandle handle, char[] buffer) { fixed (char* bufPtr = buffer) { - return Interop.Kernel32.GetFinalPathNameByHandle(handle, bufPtr, (uint)buffer.Length, Interop.Kernel32.FILE_NAME_OPENED); + return Interop.Kernel32.GetFinalPathNameByHandle(handle, bufPtr, (uint)buffer.Length, Interop.Kernel32.FILE_NAME_NORMALIZED); } } From 3ef33c2a44003f21a93b4e17d622abe98eecc71b Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Tue, 24 Aug 2021 15:03:29 -0700 Subject: [PATCH 06/19] Revert addition of FILE_NAME_OPENED const. --- .../Windows/Kernel32/Interop.GetFinalPathNameByHandle.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetFinalPathNameByHandle.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetFinalPathNameByHandle.cs index 5e01c10fa5ed73..756b1bbd72db12 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetFinalPathNameByHandle.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetFinalPathNameByHandle.cs @@ -10,9 +10,7 @@ internal static partial class Interop { internal static partial class Kernel32 { - // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/fltkernel/ns-fltkernel-_flt_file_name_information#remarks internal const uint FILE_NAME_NORMALIZED = 0x0; - internal const uint FILE_NAME_OPENED = 0x8; // https://docs.microsoft.com/windows/desktop/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew (kernel32) [DllImport(Libraries.Kernel32, EntryPoint = "GetFinalPathNameByHandleW", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)] From bc12ecf4e473b1d84f77e87eebd87e7bfd0efc69 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Wed, 25 Aug 2021 15:39:14 -0700 Subject: [PATCH 07/19] Remove unnecessary enumeration junction test. --- .../SymbolicLinks/BaseJunctions.FileSystem.cs | 43 ------------------- .../tests/Directory/Junctions.cs | 29 ------------- .../tests/DirectoryInfo/Junctions.cs | 31 ------------- 3 files changed, 103 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs index 79a42cd3a9d50d..677dcc1d0f8c82 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs @@ -21,16 +21,6 @@ protected DirectoryInfo CreateJunction(string junctionPath, string targetPath) protected abstract FileSystemInfo ResolveLinkTarget(string junctionPath, bool returnFinalTarget); - protected abstract void VerifyEnumerateMethods(string junctionPath, string[] expectedFiles, string[] expectedDirectories, string[] expectedEntries); - - protected void VerifyEnumeration(IEnumerable expectedEnumeration, IEnumerable actualEnumeration) - { - foreach (string expectedItem in expectedEnumeration) - { - Assert.True(actualEnumeration.Contains(expectedItem)); - } - } - [Fact] public void Junction_ResolveLinkTarget() { @@ -45,38 +35,5 @@ public void Junction_ResolveLinkTarget() Assert.Equal(targetPath, actualTargetInfo.FullName); Assert.Equal(targetPath, junctionInfo.LinkTarget); } - - [Fact] - public void Junction_EnumerateFileSystemEntries() - { - // Root - string targetPath = GetRandomDirPath(); - Directory.CreateDirectory(targetPath); - - string fileName = GetRandomFileName(); - string subDirName = GetRandomDirName(); - string subFileName = Path.Join(subDirName, GetRandomFileName()); - - string filePath = Path.Join(targetPath, fileName); - string subDirPath = Path.Join(targetPath, subDirName); - string subFilePath = Path.Join(targetPath, subFileName); - - File.Create(filePath).Dispose(); - Directory.CreateDirectory(subDirPath); - File.Create(subFilePath).Dispose(); - - string junctionPath = GetRandomLinkPath(); - CreateJunction(junctionPath, targetPath); - - string jFilePath = Path.Join(junctionPath, fileName); - string jSubDirPath = Path.Join(junctionPath, subDirName); - string jSubFilePath = Path.Join(junctionPath, subFileName); - - string[] expectedFiles = new[] { jFilePath, jSubFilePath }; - string[] expectedDirectories = new[] { jSubDirPath }; - string[] expectedEntries = new[] { jFilePath, jSubDirPath, jSubFilePath }; - - VerifyEnumerateMethods(junctionPath, expectedFiles, expectedDirectories, expectedEntries); - } } } diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/Junctions.cs b/src/libraries/System.IO.FileSystem/tests/Directory/Junctions.cs index 3a999d283fcc6f..19a928bdaf336a 100644 --- a/src/libraries/System.IO.FileSystem/tests/Directory/Junctions.cs +++ b/src/libraries/System.IO.FileSystem/tests/Directory/Junctions.cs @@ -13,34 +13,5 @@ protected override DirectoryInfo CreateDirectory(string path) => protected override FileSystemInfo? ResolveLinkTarget(string junctionPath, bool returnFinalTarget) => Directory.ResolveLinkTarget(junctionPath, returnFinalTarget); - - protected override void VerifyEnumerateMethods(string junctionPath, string[] expectedFiles, string[] expectedDirectories, string[] expectedEntries) - { - EnumerationOptions options = new() { RecurseSubdirectories = true }; - - VerifyEnumeration( - Directory.EnumerateFiles(junctionPath, "*", options), - expectedFiles); - - VerifyEnumeration( - Directory.EnumerateDirectories(junctionPath, "*", options), - expectedDirectories); - - VerifyEnumeration( - Directory.EnumerateFileSystemEntries(junctionPath, "*", options), - expectedEntries); - - VerifyEnumeration( - Directory.GetFiles(junctionPath, "*", options), - expectedFiles); - - VerifyEnumeration( - Directory.GetDirectories(junctionPath, "*", options), - expectedDirectories); - - VerifyEnumeration( - Directory.GetFileSystemEntries(junctionPath, "*", options), - expectedEntries); - } } } diff --git a/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/Junctions.cs b/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/Junctions.cs index 2ebb0798473932..ca16d6ef643d65 100644 --- a/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/Junctions.cs +++ b/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/Junctions.cs @@ -18,36 +18,5 @@ protected override DirectoryInfo CreateDirectory(string path) protected override FileSystemInfo? ResolveLinkTarget(string junctionPath, bool returnFinalTarget) => new DirectoryInfo(junctionPath).ResolveLinkTarget(returnFinalTarget); - - protected override void VerifyEnumerateMethods(string junctionPath, string[] expectedFiles, string[] expectedDirectories, string[] expectedEntries) - { - EnumerationOptions options = new() { RecurseSubdirectories = true }; - - DirectoryInfo info = new(junctionPath); - - VerifyEnumeration( - info.EnumerateFiles("*", options).Select(x => x.FullName), - expectedFiles); - - VerifyEnumeration( - info.EnumerateDirectories("*", options).Select(x => x.FullName), - expectedDirectories); - - VerifyEnumeration( - info.EnumerateFileSystemInfos("*", options).Select(x => x.FullName), - expectedEntries); - - VerifyEnumeration( - info.GetFiles("*", options).Select(x => x.FullName), - expectedFiles); - - VerifyEnumeration( - info.GetDirectories("*", options).Select(x => x.FullName), - expectedDirectories); - - VerifyEnumeration( - info.GetFileSystemInfos("*", options).Select(x => x.FullName), - expectedEntries); - } } } From 89577654645daedc547e4006b155f5e89548c9f8 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Wed, 25 Aug 2021 15:56:25 -0700 Subject: [PATCH 08/19] Rename GetNewCwdPath to ChangeCurrentDirectory --- .../tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs | 2 +- .../tests/Base/SymbolicLinks/BaseSymbolicLinks.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs index e4b70523c37fb7..9f61e73a36a6b1 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.FileSystem.cs @@ -463,7 +463,7 @@ private void ResolveLinkTarget_ReturnFinalTarget(string link1Path, string link1T // Must call inside a remote executor protected void CreateSymbolicLink_PathToTarget_RelativeToLinkPath_Internal(bool createOpposite) { - string tempCwd = GetNewCwdPath(); + string tempCwd = ChangeCurrentDirectory(); // Create a dummy file or directory in cwd. string fileOrDirectoryInCwd = GetRandomFileName(); diff --git a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.cs b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.cs index 1e5c7c4a3aa3e2..6e5637d8327271 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseSymbolicLinks.cs @@ -38,13 +38,12 @@ protected DirectoryInfo CreateSelfReferencingSymbolicLink() /// Important: Make sure to call this inside a remote executor to avoid changing the cwd for all tests in same process. /// /// The path of the new cwd. - protected string GetNewCwdPath() + protected string ChangeCurrentDirectory() { string tempCwd = GetRandomDirPath(); Directory.CreateDirectory(tempCwd); Directory.SetCurrentDirectory(tempCwd); return tempCwd; } - } } From ce2aa86d1e188189e5abc8046dcca5159ff04363 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Wed, 25 Aug 2021 16:01:42 -0700 Subject: [PATCH 09/19] Make Junction_ResolveLinkTarget a theory and test both resolveFinalTarget --- .../tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs index 677dcc1d0f8c82..82adffcfe509d1 100644 --- a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs +++ b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs @@ -21,8 +21,10 @@ protected DirectoryInfo CreateJunction(string junctionPath, string targetPath) protected abstract FileSystemInfo ResolveLinkTarget(string junctionPath, bool returnFinalTarget); - [Fact] - public void Junction_ResolveLinkTarget() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Junction_ResolveLinkTarget(bool returnFinalTarget) { string junctionPath = GetRandomLinkPath(); string targetPath = GetRandomDirPath(); @@ -30,7 +32,7 @@ public void Junction_ResolveLinkTarget() CreateDirectory(targetPath); DirectoryInfo junctionInfo = CreateJunction(junctionPath, targetPath); - FileSystemInfo? actualTargetInfo = ResolveLinkTarget(junctionPath, returnFinalTarget: false); + FileSystemInfo? actualTargetInfo = ResolveLinkTarget(junctionPath, returnFinalTarget); Assert.True(actualTargetInfo is DirectoryInfo); Assert.Equal(targetPath, actualTargetInfo.FullName); Assert.Equal(targetPath, junctionInfo.LinkTarget); From 999cddc3a9e7f4555677778528a1df4a831b5379 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Wed, 25 Aug 2021 16:21:36 -0700 Subject: [PATCH 10/19] Shorter name for targetPath string. Typo in comment. Fix Debug.Assert. --- .../src/System/IO/FileSystem.Windows.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 455c03ecb19d62..c85c02b243fc92 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 @@ -514,16 +514,16 @@ internal static void CreateSymbolicLink(string path, string pathToTarget, bool i int printNameOffset = sizeof(Interop.Kernel32.SymbolicLinkReparseBuffer) + rbSymlink.PrintNameOffset; int printNameLength = rbSymlink.PrintNameLength; - Span targetPathSymlink = MemoryMarshal.Cast(bufferSpan.Slice(printNameOffset, printNameLength)); - Debug.Assert((rbSymlink.Flags & Interop.Kernel32.SYMLINK_FLAG_RELATIVE) == 0 || !PathInternal.IsExtended(targetPathSymlink)); + Span targetPath = MemoryMarshal.Cast(bufferSpan.Slice(printNameOffset, printNameLength)); + Debug.Assert((rbSymlink.Flags & Interop.Kernel32.SYMLINK_FLAG_RELATIVE) == 0 || !PathInternal.IsExtended(targetPath)); if (returnFullPath && (rbSymlink.Flags & Interop.Kernel32.SYMLINK_FLAG_RELATIVE) != 0) { // Target path is relative and is for ResolveLinkTarget(), we need to append the link directory. - return Path.Join(Path.GetDirectoryName(linkPath.AsSpan()), targetPathSymlink); + return Path.Join(Path.GetDirectoryName(linkPath.AsSpan()), targetPath); } - return targetPathSymlink.ToString(); + return targetPath.ToString(); } else if (rbSymlink.ReparseTag == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT) { @@ -533,11 +533,11 @@ internal static void CreateSymbolicLink(string path, string pathToTarget, bool i int printNameOffset = sizeof(Interop.Kernel32.MountPointReparseBuffer) + rbMountPoint.PrintNameOffset; int printNameLength = rbMountPoint.PrintNameLength; - Span targetPathMountPoint = MemoryMarshal.Cast(bufferSpan.Slice(printNameOffset, printNameLength)); + Span targetPath = MemoryMarshal.Cast(bufferSpan.Slice(printNameOffset, printNameLength)); - // Unlink symlinks, mount point paths cannot be relative - Debug.Assert(!PathInternal.IsExtended(targetPathMountPoint)); - return targetPathMountPoint.ToString(); + // Unlike symlinks, mount point paths cannot be relative + Debug.Assert(!PathInternal.IsPartiallyQualified(targetPath)); + return targetPath.ToString(); } return null; From 42fb3a0d9a93b28607c7b86ca0dbf7e890824f61 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Wed, 25 Aug 2021 16:22:43 -0700 Subject: [PATCH 11/19] Clarify test comment. Change PlatformDetection for OperatingSystem check. --- .../tests/PortedCommon/ReparsePointUtilities.cs | 8 ++++---- .../tests/VirtualDriveSymbolicLinks.Windows.cs | 12 +----------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs b/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs index 182fa35c1b8263..fee6102a2b5bf0 100644 --- a/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs +++ b/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs @@ -37,8 +37,8 @@ public static bool CreateSymbolicLink(string linkPath, string targetPath, bool i if (OperatingSystem.IsWindows()) { symLinkProcess.StartInfo.FileName = "cmd"; - string option = isDirectory ? "/D" : ""; - symLinkProcess.StartInfo.Arguments = $"/c mklink {option} \"{linkPath}\" \"{targetPath}\""; + string option = isDirectory ? "/D " : ""; + symLinkProcess.StartInfo.Arguments = $"/c mklink {option}\"{linkPath}\" \"{targetPath}\""; } else { @@ -71,7 +71,7 @@ public static bool CreateJunction(string junctionPath, string targetPath) public static char CreateVirtualDrive(string targetDir) { - if (!PlatformDetection.IsWindows) + if (!OperatingSystem.IsWindows()) { throw new PlatformNotSupportedException(); } @@ -111,7 +111,7 @@ char GetRandomDriveLetter() public static void DeleteVirtualDrive(char driveLetter) { - if (!PlatformDetection.IsWindows) + if (!OperatingSystem.IsWindows()) { throw new PlatformNotSupportedException(); } diff --git a/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs b/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs index 7d1a8dbb013d95..183c80551b95be 100644 --- a/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs +++ b/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs @@ -25,7 +25,7 @@ protected override void Dispose(bool disposing) } // When the immediate target is requested (via LinkTarget or using returnFinalTarget: false), - // the target path will use the virtual drive + // the returned path will point to the target using the virtual drive path // The only case of returnFinalTarget: true that will return a target using the virtual drive, is when both // the link and the target reside in the virtual drive [Theory] @@ -255,11 +255,6 @@ private string VirtualDriveTargetDir { if (_virtualDriveTargetDir == null) { - if (!PlatformDetection.IsWindows) - { - throw new PlatformNotSupportedException(); - } - // Create a folder inside the temp directory so that it can be mounted to a drive letter with subst _virtualDriveTargetDir = Path.Join(Path.GetTempPath(), GetRandomDirName()); Directory.CreateDirectory(_virtualDriveTargetDir); @@ -277,11 +272,6 @@ private char VirtualDriveLetter { if (_virtualDriveLetter == default) { - if (!PlatformDetection.IsWindows) - { - throw new PlatformNotSupportedException(); - } - // Mount the folder to a drive letter _virtualDriveLetter = MountHelper.CreateVirtualDrive(VirtualDriveTargetDir); } From ca1f2b200ba6f392ca29088fdb6ee87a783012e4 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Wed, 25 Aug 2021 21:14:21 -0700 Subject: [PATCH 12/19] Cleaner unit tests for virtual drive, add indirection test --- .../VirtualDriveSymbolicLinks.Windows.cs | 410 +++++++++--------- 1 file changed, 195 insertions(+), 215 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs b/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs index 183c80551b95be..f94eecf86946a0 100644 --- a/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs +++ b/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs @@ -24,227 +24,207 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - // When the immediate target is requested (via LinkTarget or using returnFinalTarget: false), - // the returned path will point to the target using the virtual drive path - // The only case of returnFinalTarget: true that will return a target using the virtual drive, is when both - // the link and the target reside in the virtual drive [Theory] - [InlineData(true, true, false)] - [InlineData(false, true, false)] - [InlineData(true, false, false)] - [InlineData(true, false, true)] - public void CreateSymbolicLinkVD_VirtualPathReturned(bool linkInVD, bool targetInVD, bool returnFinalTarget) + //[InlineData(false, false, false, false)] // Target is not in virtual drive + // [InlineData(false, false, true, false)] // Target is not in virtual drive + [InlineData(false, true, false, true)] // Immediate target expected, target is in virtual drive + [InlineData(false, true, true, false)] // Final target expected, target is in virtual drive + // [InlineData(true, false, false, false)] // Target is not in virtual drive + // [InlineData(true, false, true, false)] // Target is not in virtual drive + [InlineData(true, true, false, true)] // Immediate target expected, target is in virtual drive + [InlineData(true, true, true, false)] // Final target expected, target is in virtual drive + public void SymlinkInVirtualDrive(bool isLinkInVirtualDrive, bool isTargetInVirtualDrive, bool returnFinalTarget, bool isExpectedTargetPathVirtual) { - // File - CreateSymbolicLinkVD_VirtualPathReturned_Internal( - linkInVD, - targetInVD, - returnFinalTarget, - isDirectoryTest: false, - CreateFile, - CreateFileSymbolicLink, - AssertFileIsCorrectTypeAndDirectoryAttribute); - - // Directory - CreateSymbolicLinkVD_VirtualPathReturned_Internal( - linkInVD, - targetInVD, - returnFinalTarget, - isDirectoryTest: true, - CreateDirectory, - CreateDirectorySymbolicLink, - AssertDirectoryIsCorrectTypeAndDirectoryAttribute); - - // FileInfo - CreateSymbolicLinkVD_VirtualPathReturned_Internal( - linkInVD, - targetInVD, - returnFinalTarget, - isDirectoryTest: false, - CreateFile, - CreateFileInfoSymbolicLink, - AssertFileIsCorrectTypeAndDirectoryAttribute); - - // DirectoryInfo - CreateSymbolicLinkVD_VirtualPathReturned_Internal( - linkInVD, - targetInVD, - returnFinalTarget, - isDirectoryTest: true, - CreateDirectory, - CreateDirectoryInfoSymbolicLink, - AssertDirectoryIsCorrectTypeAndDirectoryAttribute); + // File link + string fileLinkName = GetRandomLinkName(); + string fileLinkPath = Path.Join( + isLinkInVirtualDrive ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, + fileLinkName); + + // Directory link + string dirLinkName = GetRandomLinkName(); + string dirLinkPath = Path.Join( + isLinkInVirtualDrive ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, + dirLinkName); + + // File target + string fileTargetFileName = GetRandomFileName(); + string fileTargetPath = Path.Join( + isTargetInVirtualDrive ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, + fileTargetFileName); + + // Directory target + string dirTargetFileName = GetRandomDirName(); + string dirTargetPath = Path.Join( + isTargetInVirtualDrive ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, + dirTargetFileName); + + // Create targets + File.Create(fileTargetPath).Dispose(); + Directory.CreateDirectory(dirTargetPath); + + // Create links + FileInfo fileLinkInfo = new FileInfo(fileLinkPath); + fileLinkInfo.CreateAsSymbolicLink(fileTargetPath); + DirectoryInfo dirLinkInfo = new DirectoryInfo(dirLinkPath); + dirLinkInfo.CreateAsSymbolicLink(dirTargetPath); + + // The expected results depend on the target location and the value of returnFinalTarget + + // LinkTarget always retrieves the immediate target, so the expected value + // is always the path that was provided by the user for the target + string expectedFileTargetPath = fileTargetPath; + string expectedDirTargetPath = dirTargetPath; + + // Verify the LinkTarget values of the link infos + Assert.Equal(expectedFileTargetPath, fileLinkInfo.LinkTarget); + Assert.Equal(expectedDirTargetPath, dirLinkInfo.LinkTarget); + + // When the target is in a virtual drive, and returnFinalTarget is true, + // the expected target path is the real path, not the virtual path + string expectedTargetFileInfoFullName = Path.Join( + isExpectedTargetPathVirtual ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, + fileTargetFileName); + + string expectedTargetDirectoryInfoFullName = Path.Join( + isExpectedTargetPathVirtual ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, + dirTargetFileName); + + // Verify target infos from link info instances + FileSystemInfo? targetFileInfoFromFileInfoLink = fileLinkInfo.ResolveLinkTarget(returnFinalTarget); + FileSystemInfo? targetDirInfoFromDirInfoLink = dirLinkInfo.ResolveLinkTarget(returnFinalTarget); + + Assert.True(targetFileInfoFromFileInfoLink is FileInfo); + Assert.True(targetDirInfoFromDirInfoLink is DirectoryInfo); + + Assert.Equal(expectedTargetFileInfoFullName, targetFileInfoFromFileInfoLink.FullName); + Assert.Equal(expectedTargetDirectoryInfoFullName, targetDirInfoFromDirInfoLink.FullName); + + // Verify targets infos via static methods + FileSystemInfo? targetFileInfoFromFile = File.ResolveLinkTarget(fileLinkPath, returnFinalTarget); + FileSystemInfo? targetFileInfoFromDirectory = Directory.ResolveLinkTarget(dirLinkPath, returnFinalTarget); + + Assert.True(targetFileInfoFromFile is FileInfo); + Assert.True(targetFileInfoFromDirectory is DirectoryInfo); + + Assert.Equal(expectedTargetFileInfoFullName, targetFileInfoFromFile.FullName); + Assert.Equal(expectedTargetDirectoryInfoFullName, targetFileInfoFromDirectory.FullName); } - // When the link target resides in a virtual drive, and returnFinalTarget: true is used, - // All segments of the target path get resolved, including mount points, to their real path - [Theory] - [InlineData(false)] - [InlineData(true)] - public void CreateSymbolicLinkVD_ResolvedPathReturned(bool linkInVD) - { - // File - CreateSymbolicLinkVD_ResolvedPathReturned_Internal( - linkInVD, - targetInVD: true, - returnFinalTarget: true, - isDirectoryTest: false, - CreateFile, - CreateFileSymbolicLink, - AssertFileIsCorrectTypeAndDirectoryAttribute); - - //.Directory - CreateSymbolicLinkVD_ResolvedPathReturned_Internal( - linkInVD, - targetInVD: true, - returnFinalTarget: true, - isDirectoryTest: true, - CreateDirectory, - CreateDirectorySymbolicLink, - AssertDirectoryIsCorrectTypeAndDirectoryAttribute); - - // FileInfo - CreateSymbolicLinkVD_ResolvedPathReturned_Internal( - linkInVD, - targetInVD: true, - returnFinalTarget: true, - isDirectoryTest: false, - CreateFile, - CreateFileInfoSymbolicLink, - AssertFileIsCorrectTypeAndDirectoryAttribute); - - //.DirectoryInfo - CreateSymbolicLinkVD_ResolvedPathReturned_Internal( - linkInVD, - targetInVD: true, - returnFinalTarget: true, - isDirectoryTest: true, - CreateDirectory, - CreateDirectoryInfoSymbolicLink, - AssertDirectoryIsCorrectTypeAndDirectoryAttribute); - } - - private void CreateSymbolicLinkVD_VirtualPathReturned_Internal( - bool linkInVD, - bool targetInVD, - bool returnFinalTarget, - bool isDirectoryTest, - TargetCreationMethod targetCreationMethod, - SymbolicLinkCreationMethod symbolicLinkCreationMethod, - AssertIsCorrectTypeAndDirectoryAttributeMethod assertIsCorrectTypeAndDirectoryAttributeMethod) - { - (string targetPath, FileSystemInfo linkInfo, FileSystemInfo targetInfo) = - CreateSymbolicLinkVD_Internal( - linkInVD, - targetInVD, - returnFinalTarget, - isDirectoryTest, - targetCreationMethod, - symbolicLinkCreationMethod, - assertIsCorrectTypeAndDirectoryAttributeMethod); - - Assert.Equal(targetPath, linkInfo.LinkTarget); - Assert.Equal(targetPath, targetInfo.FullName); - } - - private void CreateSymbolicLinkVD_ResolvedPathReturned_Internal( - bool linkInVD, - bool targetInVD, - bool returnFinalTarget, - bool isDirectoryTest, - TargetCreationMethod targetCreationMethod, - SymbolicLinkCreationMethod symbolicLinkCreationMethod, - AssertIsCorrectTypeAndDirectoryAttributeMethod assertIsCorrectTypeAndDirectoryAttributeMethod) - { - (string targetPath, FileSystemInfo linkInfo, FileSystemInfo targetInfo) = - CreateSymbolicLinkVD_Internal( - linkInVD, - targetInVD, - returnFinalTarget, - isDirectoryTest, - targetCreationMethod, - symbolicLinkCreationMethod, - assertIsCorrectTypeAndDirectoryAttributeMethod); - - Assert.Equal(targetPath, linkInfo.LinkTarget); - string resolvedTargetPath = Path.Join(VirtualDriveTargetDir, Path.GetFileName(targetPath)); - Assert.Equal(resolvedTargetPath, targetInfo.FullName); - } - - private (string, FileSystemInfo, FileSystemInfo) CreateSymbolicLinkVD_Internal( - bool linkInVD, - bool targetInVD, - bool returnFinalTarget, - bool isDirectoryTest, - TargetCreationMethod createFileOrDirectory, - SymbolicLinkCreationMethod createSymbolicLink, - AssertIsCorrectTypeAndDirectoryAttributeMethod assertIsCorrectTypeAndDirectoryAttribute) - { - string targetFileName = isDirectoryTest ? GetRandomDirName() : GetRandomFileName(); - string pathToTarget = Path.Join( - targetInVD ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, - targetFileName); - - string linkFileName = GetRandomLinkName(); - string linkPath = Path.Join( - linkInVD ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, - linkFileName); - - createFileOrDirectory(pathToTarget); - - FileSystemInfo? link = createSymbolicLink(linkPath, pathToTarget); - assertIsCorrectTypeAndDirectoryAttribute(link); - - FileSystemInfo? target = link.ResolveLinkTarget(returnFinalTarget); - assertIsCorrectTypeAndDirectoryAttribute(target); - - return (pathToTarget, link, target); - } - - private delegate void TargetCreationMethod(string targetPath); - - private void CreateFile(string targetPath) => File.Create(targetPath).Dispose(); - private void CreateDirectory(string targetPath) => Directory.CreateDirectory(targetPath); - - private delegate FileSystemInfo SymbolicLinkCreationMethod(string path, string pathToTarget); - - private FileSystemInfo CreateFileSymbolicLink(string path, string pathToTarget) => File.CreateSymbolicLink(path, pathToTarget); - - private FileSystemInfo CreateDirectorySymbolicLink(string path, string pathToTarget) => Directory.CreateSymbolicLink(path, pathToTarget); - - private FileSystemInfo CreateFileInfoSymbolicLink(string path, string pathToTarget) - { - FileInfo link = new(path); - link.CreateAsSymbolicLink(pathToTarget); - return link; - } - - private FileSystemInfo CreateDirectoryInfoSymbolicLink(string path, string pathToTarget) - { - DirectoryInfo link = new(path); - link.CreateAsSymbolicLink(pathToTarget); - return link; - } - - private delegate void AssertIsCorrectTypeAndDirectoryAttributeMethod(FileSystemInfo link); - - private void AssertFileIsCorrectTypeAndDirectoryAttribute(FileSystemInfo link) - { - if (link.Exists) - { - Assert.False(link.Attributes.HasFlag(FileAttributes.Directory)); - } - Assert.True(link is FileInfo); - } - - private void AssertDirectoryIsCorrectTypeAndDirectoryAttribute(FileSystemInfo link) + [Theory] + //[InlineData(false, false, false, false, false)] // Target is not in virtual drive + // [InlineData(false, false, false, true, false)] // Target is not in virtual drive + [InlineData(false, false, true, false, false)] // Immediate target expected, middle link is NOT in virtual drive + [InlineData(false, false, true, true, false)] // Final target expected, target is in virtual drive + //[InlineData(false, true, false, false, false)] // Target is not in virtual drive + // [InlineData(false, true, false, true, false)] // Target is not in virtual drive + [InlineData(false, true, true, false, true)] // Immediate target expected, target is in virtual drive + [InlineData(false, true, true, true, false)] // Final target expected, target is in virtual drive + // [InlineData(true, false, false, false, false)] // Target is not in virtual drive + // [InlineData(true, false, false, true, false)] // Target is not in virtual drive + [InlineData(true, false, true, false, false)] // Immediate target expected, middle link is NOT in virtual drive + [InlineData(true, false, true, true, false)] // Final target expected, target is in virtual drive + // [InlineData(true, true, false, false, false)] // Target is not in virtual drive + // [InlineData(true, true, false, true, false)] // Target is not in virtual drive + [InlineData(true, true, true, false, true)] // Immediate target expected, target is in virtual drive + [InlineData(true, true, true, true, false)] // Final target expected, target is in virtual drive + public void SymlinkInVirtualDrive_WithIndirection(bool isFirstLinkInVirtualDrive, bool isMiddleLinkInVirtualDrive, bool isTargetInVirtualDrive, bool returnFinalTarget, bool isExpectedTargetPathVirtual) { - if (link.Exists) - { - Assert.True(link.Attributes.HasFlag(FileAttributes.Directory)); - } - Assert.True(link is DirectoryInfo); + // File link + string fileLinkName = GetRandomLinkName(); + string fileLinkPath = Path.Join( + isFirstLinkInVirtualDrive ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, + fileLinkName); + + // Directory link + string dirLinkName = GetRandomLinkName(); + string dirLinkPath = Path.Join( + isFirstLinkInVirtualDrive ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, + dirLinkName); + + // File middle link + string fileMiddleLinkFileName = GetRandomLinkName(); + string fileMiddleLinkPath = Path.Join( + isMiddleLinkInVirtualDrive ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, + fileMiddleLinkFileName); + + // Directory middle link + string dirMiddleLinkFileName = GetRandomLinkName(); + string dirMiddleLinkPath = Path.Join( + isMiddleLinkInVirtualDrive ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, + dirMiddleLinkFileName); + + // File final target + string fileFinalTargetFileName = GetRandomFileName(); + string fileFinalTargetPath = Path.Join( + isTargetInVirtualDrive ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, + fileFinalTargetFileName); + + // Directory final target + string dirFinalTargetFileName = GetRandomDirName(); + string dirFinalTargetPath = Path.Join( + isTargetInVirtualDrive ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, + dirFinalTargetFileName); + + // Create targets + File.Create(fileFinalTargetPath).Dispose(); + Directory.CreateDirectory(dirFinalTargetPath); + + // Create initial links + FileInfo fileLinkInfo = new FileInfo(fileLinkPath); + fileLinkInfo.CreateAsSymbolicLink(fileMiddleLinkPath); + DirectoryInfo dirLinkInfo = new DirectoryInfo(dirLinkPath); + dirLinkInfo.CreateAsSymbolicLink(dirMiddleLinkPath); + + // Create middle links + FileInfo fileMiddleLinkInfo = new FileInfo(fileMiddleLinkPath); + fileMiddleLinkInfo.CreateAsSymbolicLink(fileFinalTargetPath); + DirectoryInfo dirMiddleLinkInfo = new DirectoryInfo(dirMiddleLinkPath); + dirMiddleLinkInfo.CreateAsSymbolicLink(dirFinalTargetPath); + + // The expected results depend on the target location and the value of returnFinalTarget + + // LinkTarget always retrieves the immediate target, so the expected value + // is always the path that was provided by the user for the middle link + string expectedFileTargetPath = fileMiddleLinkPath; + string expectedDirTargetPath = dirMiddleLinkPath; + + // Verify the LinkTarget values of the link infos + Assert.Equal(expectedFileTargetPath, fileLinkInfo.LinkTarget); + Assert.Equal(expectedDirTargetPath, dirLinkInfo.LinkTarget); + + // When the target is in a virtual drive, + // the expected target path is the real path, not the virtual path + // When returnFinalTarget is true, the expected target path is the + // resolved path from the final target in the chain of links + string expectedTargetFileInfoFullName = Path.Join( + isExpectedTargetPathVirtual ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, + returnFinalTarget ? fileFinalTargetFileName : fileMiddleLinkFileName); + + string expectedTargetDirectoryInfoFullName = Path.Join( + isExpectedTargetPathVirtual ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, + returnFinalTarget ? dirFinalTargetFileName : dirMiddleLinkFileName); + + // Verify target infos from link info instances + FileSystemInfo? targetFileInfoFromFileInfoLink = fileLinkInfo.ResolveLinkTarget(returnFinalTarget); + FileSystemInfo? targetDirInfoFromDirInfoLink = dirLinkInfo.ResolveLinkTarget(returnFinalTarget); + + Assert.True(targetFileInfoFromFileInfoLink is FileInfo); + Assert.True(targetDirInfoFromDirInfoLink is DirectoryInfo); + + Assert.Equal(expectedTargetFileInfoFullName, targetFileInfoFromFileInfoLink.FullName); + Assert.Equal(expectedTargetDirectoryInfoFullName, targetDirInfoFromDirInfoLink.FullName); + + // Verify targets infos via static methods + FileSystemInfo? targetFileInfoFromFile = File.ResolveLinkTarget(fileLinkPath, returnFinalTarget); + FileSystemInfo? targetFileInfoFromDirectory = Directory.ResolveLinkTarget(dirLinkPath, returnFinalTarget); + + Assert.True(targetFileInfoFromFile is FileInfo); + Assert.True(targetFileInfoFromDirectory is DirectoryInfo); + + Assert.Equal(expectedTargetFileInfoFullName, targetFileInfoFromFile.FullName); + Assert.Equal(expectedTargetDirectoryInfoFullName, targetFileInfoFromDirectory.FullName); } // Temporary Windows directory that can be mounted to a drive letter using the subst command From 6f6d368233585fb75fc2f591fc020d3f053f1836 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Thu, 26 Aug 2021 05:13:00 -0700 Subject: [PATCH 13/19] Skip virtual drive tests in Windows Nano (subst not available). Small test rename. --- .../tests/VirtualDriveSymbolicLinks.Windows.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs b/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs index f94eecf86946a0..66d6467b47c318 100644 --- a/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs +++ b/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs @@ -8,6 +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(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] public class VirtualDrive_SymbolicLinks : BaseSymbolicLinks { protected override void Dispose(bool disposing) @@ -33,7 +34,7 @@ protected override void Dispose(bool disposing) // [InlineData(true, false, true, false)] // Target is not in virtual drive [InlineData(true, true, false, true)] // Immediate target expected, target is in virtual drive [InlineData(true, true, true, false)] // Final target expected, target is in virtual drive - public void SymlinkInVirtualDrive(bool isLinkInVirtualDrive, bool isTargetInVirtualDrive, bool returnFinalTarget, bool isExpectedTargetPathVirtual) + public void VirtualDrive_SymbolicLinks_LinkAndTarget(bool isLinkInVirtualDrive, bool isTargetInVirtualDrive, bool returnFinalTarget, bool isExpectedTargetPathVirtual) { // File link string fileLinkName = GetRandomLinkName(); @@ -129,7 +130,7 @@ public void SymlinkInVirtualDrive(bool isLinkInVirtualDrive, bool isTargetInVirt // [InlineData(true, true, false, true, false)] // Target is not in virtual drive [InlineData(true, true, true, false, true)] // Immediate target expected, target is in virtual drive [InlineData(true, true, true, true, false)] // Final target expected, target is in virtual drive - public void SymlinkInVirtualDrive_WithIndirection(bool isFirstLinkInVirtualDrive, bool isMiddleLinkInVirtualDrive, bool isTargetInVirtualDrive, bool returnFinalTarget, bool isExpectedTargetPathVirtual) + public void VirtualDrive_SymbolicLinks_WithIndirection(bool isFirstLinkInVirtualDrive, bool isMiddleLinkInVirtualDrive, bool isTargetInVirtualDrive, bool returnFinalTarget, bool isExpectedTargetPathVirtual) { // File link string fileLinkName = GetRandomLinkName(); From ec8000b91d920953053a109f24c93c830489c8cb Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Thu, 26 Aug 2021 05:25:25 -0700 Subject: [PATCH 14/19] Simplify Junctions tests, add indirection test --- .../SymbolicLinks/BaseJunctions.FileSystem.cs | 41 ------------ .../tests/Directory/Junctions.cs | 17 ----- .../tests/DirectoryInfo/Junctions.cs | 22 ------- .../tests/Junctions.Windows.cs | 62 +++++++++++++++++++ .../tests/System.IO.FileSystem.Tests.csproj | 4 +- 5 files changed, 63 insertions(+), 83 deletions(-) delete mode 100644 src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs delete mode 100644 src/libraries/System.IO.FileSystem/tests/Directory/Junctions.cs delete mode 100644 src/libraries/System.IO.FileSystem/tests/DirectoryInfo/Junctions.cs create mode 100644 src/libraries/System.IO.FileSystem/tests/Junctions.Windows.cs diff --git a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs b/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs deleted file mode 100644 index 82adffcfe509d1..00000000000000 --- a/src/libraries/System.IO.FileSystem/tests/Base/SymbolicLinks/BaseJunctions.FileSystem.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.Linq; -using Xunit; - -namespace System.IO.Tests -{ - [PlatformSpecific(TestPlatforms.Windows)] - public abstract class BaseJunctions_FileSystem : BaseSymbolicLinks - { - protected DirectoryInfo CreateJunction(string junctionPath, string targetPath) - { - Assert.True(MountHelper.CreateJunction(junctionPath, targetPath)); - DirectoryInfo junctionInfo = new(junctionPath); - return junctionInfo; - } - - protected abstract DirectoryInfo CreateDirectory(string path); - - protected abstract FileSystemInfo ResolveLinkTarget(string junctionPath, bool returnFinalTarget); - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Junction_ResolveLinkTarget(bool returnFinalTarget) - { - string junctionPath = GetRandomLinkPath(); - string targetPath = GetRandomDirPath(); - - CreateDirectory(targetPath); - DirectoryInfo junctionInfo = CreateJunction(junctionPath, targetPath); - - FileSystemInfo? actualTargetInfo = ResolveLinkTarget(junctionPath, returnFinalTarget); - Assert.True(actualTargetInfo is DirectoryInfo); - Assert.Equal(targetPath, actualTargetInfo.FullName); - Assert.Equal(targetPath, junctionInfo.LinkTarget); - } - } -} diff --git a/src/libraries/System.IO.FileSystem/tests/Directory/Junctions.cs b/src/libraries/System.IO.FileSystem/tests/Directory/Junctions.cs deleted file mode 100644 index 19a928bdaf336a..00000000000000 --- a/src/libraries/System.IO.FileSystem/tests/Directory/Junctions.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Xunit; - -namespace System.IO.Tests -{ - [PlatformSpecific(TestPlatforms.Windows)] - public class Directory_Junctions : BaseJunctions_FileSystem - { - protected override DirectoryInfo CreateDirectory(string path) => - Directory.CreateDirectory(path); - - protected override FileSystemInfo? ResolveLinkTarget(string junctionPath, bool returnFinalTarget) => - Directory.ResolveLinkTarget(junctionPath, returnFinalTarget); - } -} diff --git a/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/Junctions.cs b/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/Junctions.cs deleted file mode 100644 index ca16d6ef643d65..00000000000000 --- a/src/libraries/System.IO.FileSystem/tests/DirectoryInfo/Junctions.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Linq; -using Xunit; - -namespace System.IO.Tests -{ - [PlatformSpecific(TestPlatforms.Windows)] - public class DirectoryInfo_Junctions : BaseJunctions_FileSystem - { - protected override DirectoryInfo CreateDirectory(string path) - { - DirectoryInfo dirInfo = new(path); - dirInfo.Create(); - return dirInfo; - } - - protected override FileSystemInfo? ResolveLinkTarget(string junctionPath, bool returnFinalTarget) => - new DirectoryInfo(junctionPath).ResolveLinkTarget(returnFinalTarget); - } -} diff --git a/src/libraries/System.IO.FileSystem/tests/Junctions.Windows.cs b/src/libraries/System.IO.FileSystem/tests/Junctions.Windows.cs new file mode 100644 index 00000000000000..60cb570e206bf1 --- /dev/null +++ b/src/libraries/System.IO.FileSystem/tests/Junctions.Windows.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace System.IO.Tests +{ + [PlatformSpecific(TestPlatforms.Windows)] + public class Junctions : BaseSymbolicLinks + { + protected DirectoryInfo CreateJunction(string junctionPath, string targetPath) + { + Assert.True(MountHelper.CreateJunction(junctionPath, targetPath)); + DirectoryInfo junctionInfo = new(junctionPath); + return junctionInfo; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Junction_ResolveLinkTarget(bool returnFinalTarget) + { + string junctionPath = GetRandomLinkPath(); + string targetPath = GetRandomDirPath(); + + Directory.CreateDirectory(targetPath); + DirectoryInfo junctionInfo = CreateJunction(junctionPath, targetPath); + + FileSystemInfo? actualTargetInfo = junctionInfo.ResolveLinkTarget(returnFinalTarget); + Assert.True(actualTargetInfo is DirectoryInfo); + Assert.Equal(targetPath, actualTargetInfo.FullName); + Assert.Equal(targetPath, junctionInfo.LinkTarget); + } + + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Junction_ResolveLinkTarget_WithIndirection(bool returnFinalTarget) + { + string firstJunctionPath = GetRandomLinkPath(); + string middleJunctionPath = GetRandomLinkPath(); + string targetPath = GetRandomDirPath(); + + Directory.CreateDirectory(targetPath); + _ = CreateJunction(middleJunctionPath, targetPath); + DirectoryInfo firstJunctionInfo = CreateJunction(firstJunctionPath, middleJunctionPath); + + string expectedTargetPath = returnFinalTarget ? targetPath : middleJunctionPath; + + FileSystemInfo? actualTargetInfo = firstJunctionInfo.ResolveLinkTarget(returnFinalTarget); + + Assert.True(actualTargetInfo is DirectoryInfo); + Assert.Equal(expectedTargetPath, actualTargetInfo.FullName); + + // Always the immediate target + Assert.Equal(middleJunctionPath, firstJunctionInfo.LinkTarget); + } + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj index 8f467d0ed2a5ba..bb7cac297d73a3 100644 --- a/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj +++ b/src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj @@ -75,14 +75,12 @@ - - - + From 7df549e06b6e58f02d74b16edef60fe7530cb241 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Thu, 26 Aug 2021 13:51:56 -0700 Subject: [PATCH 15/19] Address test suggestions. --- .../tests/Junctions.Windows.cs | 25 ++- .../PortedCommon/ReparsePointUtilities.cs | 91 ++++++----- .../VirtualDriveSymbolicLinks.Windows.cs | 146 ++++++++---------- 3 files changed, 131 insertions(+), 131 deletions(-) diff --git a/src/libraries/System.IO.FileSystem/tests/Junctions.Windows.cs b/src/libraries/System.IO.FileSystem/tests/Junctions.Windows.cs index 60cb570e206bf1..f4f2150b803149 100644 --- a/src/libraries/System.IO.FileSystem/tests/Junctions.Windows.cs +++ b/src/libraries/System.IO.FileSystem/tests/Junctions.Windows.cs @@ -28,12 +28,17 @@ public void Junction_ResolveLinkTarget(bool returnFinalTarget) Directory.CreateDirectory(targetPath); DirectoryInfo junctionInfo = CreateJunction(junctionPath, targetPath); - FileSystemInfo? actualTargetInfo = junctionInfo.ResolveLinkTarget(returnFinalTarget); - Assert.True(actualTargetInfo is DirectoryInfo); - Assert.Equal(targetPath, actualTargetInfo.FullName); + FileSystemInfo? targetFromDirectoryInfo = junctionInfo.ResolveLinkTarget(returnFinalTarget); + FileSystemInfo? targetFromDirectory = Directory.ResolveLinkTarget(junctionPath, returnFinalTarget); + + Assert.True(targetFromDirectoryInfo is DirectoryInfo); + Assert.True(targetFromDirectory is DirectoryInfo); + Assert.Equal(targetPath, junctionInfo.LinkTarget); - } + Assert.Equal(targetPath, targetFromDirectoryInfo.FullName); + Assert.Equal(targetPath, targetFromDirectory.FullName); + } [Theory] [InlineData(false)] @@ -45,18 +50,22 @@ public void Junction_ResolveLinkTarget_WithIndirection(bool returnFinalTarget) string targetPath = GetRandomDirPath(); Directory.CreateDirectory(targetPath); - _ = CreateJunction(middleJunctionPath, targetPath); + CreateJunction(middleJunctionPath, targetPath); DirectoryInfo firstJunctionInfo = CreateJunction(firstJunctionPath, middleJunctionPath); string expectedTargetPath = returnFinalTarget ? targetPath : middleJunctionPath; - FileSystemInfo? actualTargetInfo = firstJunctionInfo.ResolveLinkTarget(returnFinalTarget); + FileSystemInfo? targetFromDirectoryInfo = firstJunctionInfo.ResolveLinkTarget(returnFinalTarget); + FileSystemInfo? targetFromDirectory = Directory.ResolveLinkTarget(firstJunctionPath, returnFinalTarget); - Assert.True(actualTargetInfo is DirectoryInfo); - Assert.Equal(expectedTargetPath, actualTargetInfo.FullName); + Assert.True(targetFromDirectoryInfo is DirectoryInfo); + Assert.True(targetFromDirectory is DirectoryInfo); // Always the immediate target Assert.Equal(middleJunctionPath, firstJunctionInfo.LinkTarget); + + Assert.Equal(expectedTargetPath, targetFromDirectoryInfo.FullName); + Assert.Equal(expectedTargetPath, targetFromDirectory.FullName); } } } diff --git a/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs b/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs index fee6102a2b5bf0..25fa696a73c7c7 100644 --- a/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs +++ b/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs @@ -28,30 +28,26 @@ public static class MountHelper [DllImport("kernel32.dll", EntryPoint = "DeleteVolumeMountPointW", CharSet = CharSet.Unicode, BestFitMapping = false, SetLastError = true)] private static extern bool DeleteVolumeMountPoint(string mountPoint); - /// Creates a symbolic link using command line tools - /// The existing file - /// + /// Creates a symbolic link using command line tools. public static bool CreateSymbolicLink(string linkPath, string targetPath, bool isDirectory) { - Process symLinkProcess = new Process(); + string fileName; + string[] arguments; if (OperatingSystem.IsWindows()) { - symLinkProcess.StartInfo.FileName = "cmd"; - string option = isDirectory ? "/D " : ""; - symLinkProcess.StartInfo.Arguments = $"/c mklink {option}\"{linkPath}\" \"{targetPath}\""; + fileName = "cmd"; + arguments = new[] { "/c", "mklink", isDirectory ? "/D" : "", linkPath, targetPath }; } else { - symLinkProcess.StartInfo.FileName = "/bin/ln"; - symLinkProcess.StartInfo.Arguments = $"-s \"{targetPath}\" \"{linkPath}\""; + fileName = "/bin/ln"; + arguments = new[] { "-s", targetPath, linkPath }; } - symLinkProcess.StartInfo.UseShellExecute = false; - symLinkProcess.StartInfo.RedirectStandardOutput = true; - symLinkProcess.Start(); - symLinkProcess.WaitForExit(); - return (0 == symLinkProcess.ExitCode); + + return RunProcess(CreateStartInfo(fileName, arguments)); } + /// On Windows, creates a junction using command line tools. public static bool CreateJunction(string junctionPath, string targetPath) { if (!OperatingSystem.IsWindows()) @@ -59,16 +55,13 @@ public static bool CreateJunction(string junctionPath, string targetPath) throw new PlatformNotSupportedException(); } - Process junctionProcess = new Process(); - junctionProcess.StartInfo.FileName = "cmd"; - junctionProcess.StartInfo.Arguments = $"/c mklink /J \"{junctionPath}\" \"{targetPath}\""; - junctionProcess.StartInfo.UseShellExecute = false; - junctionProcess.StartInfo.RedirectStandardOutput = true; - junctionProcess.Start(); - junctionProcess.WaitForExit(); - return (0 == junctionProcess.ExitCode); + return RunProcess(CreateStartInfo("cmd", "/c", "mklink", "/J", junctionPath, targetPath)); } + /// + /// On Windows, mounts a folder to an assigned virtual drive letter using the subst command. + /// subst is not available in Windows Nano. + /// public static char CreateVirtualDrive(string targetDir) { if (!OperatingSystem.IsWindows()) @@ -76,22 +69,16 @@ public static char CreateVirtualDrive(string targetDir) throw new PlatformNotSupportedException(); } - char driveLetter = GetRandomDriveLetter(); - - Process substProcess = new Process(); - substProcess.StartInfo.FileName = "subst"; - substProcess.StartInfo.Arguments = $"{driveLetter}: {targetDir}"; - substProcess.StartInfo.UseShellExecute = false; - substProcess.StartInfo.RedirectStandardOutput = true; - substProcess.Start(); - substProcess.WaitForExit(); - if (!DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter)) + char driveLetter = GetNextAvailableDriveLetter(); + bool success = RunProcess(CreateStartInfo("subst", $"{driveLetter}:", targetDir)); + if (!success || !DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter)) { throw new InvalidOperationException($"Could not create virtual drive {driveLetter}: with subst"); } return driveLetter; - char GetRandomDriveLetter() + // Finds the next unused drive letter and returns it. + char GetNextAvailableDriveLetter() { List existingDrives = DriveInfo.GetDrives().Select(x => x.Name[0]).ToList(); @@ -109,6 +96,9 @@ char GetRandomDriveLetter() } } + /// + /// On Windows, unassigns the specified virtual drive letter from its mounted folder. + /// public static void DeleteVirtualDrive(char driveLetter) { if (!OperatingSystem.IsWindows()) @@ -116,14 +106,8 @@ public static void DeleteVirtualDrive(char driveLetter) throw new PlatformNotSupportedException(); } - Process substProcess = new Process(); - substProcess.StartInfo.FileName = "subst"; - substProcess.StartInfo.Arguments = $"/d {driveLetter}:"; - substProcess.StartInfo.UseShellExecute = false; - substProcess.StartInfo.RedirectStandardOutput = true; - substProcess.Start(); - substProcess.WaitForExit(); - if (DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter)) + bool success = RunProcess(CreateStartInfo("subst", "/d", $"{driveLetter}:")); + if (!success || DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter)) { throw new InvalidOperationException($"Could not delete virtual drive {driveLetter}: with subst"); } @@ -131,7 +115,6 @@ public static void DeleteVirtualDrive(char driveLetter) public static void Mount(string volumeName, string mountPoint) { - if (volumeName[volumeName.Length - 1] != Path.DirectorySeparatorChar) volumeName += Path.DirectorySeparatorChar; if (mountPoint[mountPoint.Length - 1] != Path.DirectorySeparatorChar) @@ -163,6 +146,30 @@ public static void Unmount(string mountPoint) throw new Exception(string.Format("Win32 error: {0}", Marshal.GetLastPInvokeError())); } + private static ProcessStartInfo CreateStartInfo(string fileName, params string[] arguments) + { + var info = new ProcessStartInfo + { + FileName = fileName, + UseShellExecute = false, + RedirectStandardOutput = true + }; + + foreach (var argument in arguments) + { + info.ArgumentList.Add(argument); + } + + return info; + } + + private static bool RunProcess(ProcessStartInfo startInfo) + { + var process = Process.Start(startInfo); + process.WaitForExit(); + return process.ExitCode == 0; + } + /// For standalone debugging help. Change Main0 to Main public static void Main0(string[] args) { diff --git a/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs b/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs index 66d6467b47c318..0c4f245691c96d 100644 --- a/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs +++ b/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs @@ -26,39 +26,35 @@ protected override void Dispose(bool disposing) } [Theory] - //[InlineData(false, false, false, false)] // Target is not in virtual drive - // [InlineData(false, false, true, false)] // Target is not in virtual drive - [InlineData(false, true, false, true)] // Immediate target expected, target is in virtual drive - [InlineData(false, true, true, false)] // Final target expected, target is in virtual drive - // [InlineData(true, false, false, false)] // Target is not in virtual drive - // [InlineData(true, false, true, false)] // Target is not in virtual drive - [InlineData(true, true, false, true)] // Immediate target expected, target is in virtual drive - [InlineData(true, true, true, false)] // Final target expected, target is in virtual drive - public void VirtualDrive_SymbolicLinks_LinkAndTarget(bool isLinkInVirtualDrive, bool isTargetInVirtualDrive, bool returnFinalTarget, bool isExpectedTargetPathVirtual) + // false, false, false, false // Target is not in virtual drive + // false, false, true, false // Target is not in virtual drive + [InlineData(false, true, false, true)] // Immediate target expected, target is in virtual drive + [InlineData(false, true, true, false)] // Final target expected, target is in virtual drive + // true, false, false, false // Target is not in virtual drive + // true, false, true, false // Target is not in virtual drive + [InlineData(true, true, false, true)] // Immediate target expected, target is in virtual drive + [InlineData(true, true, true, false)] // Final target expected, target is in virtual drive + public void VirtualDrive_SymbolicLinks_LinkAndTarget( + bool isLinkInVirtualDrive, + bool isTargetInVirtualDrive, + bool returnFinalTarget, + bool isExpectedTargetPathVirtual) { + string linkExpectedFolderPath = GetVirtualOrRealPath(isLinkInVirtualDrive); // File link string fileLinkName = GetRandomLinkName(); - string fileLinkPath = Path.Join( - isLinkInVirtualDrive ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, - fileLinkName); - + string fileLinkPath = Path.Join(linkExpectedFolderPath, fileLinkName); // Directory link string dirLinkName = GetRandomLinkName(); - string dirLinkPath = Path.Join( - isLinkInVirtualDrive ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, - dirLinkName); + string dirLinkPath = Path.Join(linkExpectedFolderPath, dirLinkName); + string targetExpectedFolderPath = GetVirtualOrRealPath(isTargetInVirtualDrive); // File target string fileTargetFileName = GetRandomFileName(); - string fileTargetPath = Path.Join( - isTargetInVirtualDrive ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, - fileTargetFileName); - + string fileTargetPath = Path.Join(targetExpectedFolderPath, fileTargetFileName); // Directory target string dirTargetFileName = GetRandomDirName(); - string dirTargetPath = Path.Join( - isTargetInVirtualDrive ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, - dirTargetFileName); + string dirTargetPath = Path.Join(targetExpectedFolderPath, dirTargetFileName); // Create targets File.Create(fileTargetPath).Dispose(); @@ -74,22 +70,17 @@ public void VirtualDrive_SymbolicLinks_LinkAndTarget(bool isLinkInVirtualDrive, // LinkTarget always retrieves the immediate target, so the expected value // is always the path that was provided by the user for the target - string expectedFileTargetPath = fileTargetPath; - string expectedDirTargetPath = dirTargetPath; // Verify the LinkTarget values of the link infos - Assert.Equal(expectedFileTargetPath, fileLinkInfo.LinkTarget); - Assert.Equal(expectedDirTargetPath, dirLinkInfo.LinkTarget); + Assert.Equal(fileTargetPath, fileLinkInfo.LinkTarget); + Assert.Equal(dirTargetPath, dirLinkInfo.LinkTarget); // When the target is in a virtual drive, and returnFinalTarget is true, // the expected target path is the real path, not the virtual path - string expectedTargetFileInfoFullName = Path.Join( - isExpectedTargetPathVirtual ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, - fileTargetFileName); + string expectedTargetPath = GetVirtualOrRealPath(isExpectedTargetPathVirtual); - string expectedTargetDirectoryInfoFullName = Path.Join( - isExpectedTargetPathVirtual ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, - dirTargetFileName); + string expectedTargetFileInfoFullName = Path.Join(expectedTargetPath, fileTargetFileName); + string expectedTargetDirectoryInfoFullName = Path.Join(expectedTargetPath, dirTargetFileName); // Verify target infos from link info instances FileSystemInfo? targetFileInfoFromFileInfoLink = fileLinkInfo.ResolveLinkTarget(returnFinalTarget); @@ -114,59 +105,50 @@ public void VirtualDrive_SymbolicLinks_LinkAndTarget(bool isLinkInVirtualDrive, [Theory] - //[InlineData(false, false, false, false, false)] // Target is not in virtual drive - // [InlineData(false, false, false, true, false)] // Target is not in virtual drive - [InlineData(false, false, true, false, false)] // Immediate target expected, middle link is NOT in virtual drive - [InlineData(false, false, true, true, false)] // Final target expected, target is in virtual drive - //[InlineData(false, true, false, false, false)] // Target is not in virtual drive - // [InlineData(false, true, false, true, false)] // Target is not in virtual drive - [InlineData(false, true, true, false, true)] // Immediate target expected, target is in virtual drive - [InlineData(false, true, true, true, false)] // Final target expected, target is in virtual drive - // [InlineData(true, false, false, false, false)] // Target is not in virtual drive - // [InlineData(true, false, false, true, false)] // Target is not in virtual drive - [InlineData(true, false, true, false, false)] // Immediate target expected, middle link is NOT in virtual drive - [InlineData(true, false, true, true, false)] // Final target expected, target is in virtual drive - // [InlineData(true, true, false, false, false)] // Target is not in virtual drive - // [InlineData(true, true, false, true, false)] // Target is not in virtual drive - [InlineData(true, true, true, false, true)] // Immediate target expected, target is in virtual drive - [InlineData(true, true, true, true, false)] // Final target expected, target is in virtual drive - public void VirtualDrive_SymbolicLinks_WithIndirection(bool isFirstLinkInVirtualDrive, bool isMiddleLinkInVirtualDrive, bool isTargetInVirtualDrive, bool returnFinalTarget, bool isExpectedTargetPathVirtual) + // false, false, false, false, false // Target is not in virtual drive + // false, false, false, true, false // Target is not in virtual drive + [InlineData(false, false, true, false, false)] // Immediate target expected, middle link is NOT in virtual drive + [InlineData(false, false, true, true, false)] // Final target expected, target is in virtual drive + // false, true, false, false, false // Target is not in virtual drive + // false, true, false, true, false // Target is not in virtual drive + [InlineData(false, true, true, false, true)] // Immediate target expected, target is in virtual drive + [InlineData(false, true, true, true, false)] // Final target expected, target is in virtual drive + // true, false, false, false, false // Target is not in virtual drive + // true, false, false, true, false // Target is not in virtual drive + [InlineData(true, false, true, false, false)] // Immediate target expected, middle link is NOT in virtual drive + [InlineData(true, false, true, true, false)] // Final target expected, target is in virtual drive + // true, true, false, false, false // Target is not in virtual drive + // true, true, false, true, false // Target is not in virtual drive + [InlineData(true, true, true, false, true)] // Immediate target expected, target is in virtual drive + [InlineData(true, true, true, true, false)] // Final target expected, target is in virtual drive + public void VirtualDrive_SymbolicLinks_WithIndirection( + bool isFirstLinkInVirtualDrive, + bool isMiddleLinkInVirtualDrive, + bool isTargetInVirtualDrive, + bool returnFinalTarget, + bool isExpectedTargetPathVirtual) { + string firstLinkExpectedFolderPath = GetVirtualOrRealPath(isFirstLinkInVirtualDrive); // File link - string fileLinkName = GetRandomLinkName(); - string fileLinkPath = Path.Join( - isFirstLinkInVirtualDrive ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, - fileLinkName); - + string fileLinkPath = Path.Join(firstLinkExpectedFolderPath, GetRandomLinkName()); // Directory link - string dirLinkName = GetRandomLinkName(); - string dirLinkPath = Path.Join( - isFirstLinkInVirtualDrive ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, - dirLinkName); + string dirLinkPath = Path.Join(firstLinkExpectedFolderPath, GetRandomLinkName()); + string middleLinkExpectedFolderPath = GetVirtualOrRealPath(isMiddleLinkInVirtualDrive); // File middle link string fileMiddleLinkFileName = GetRandomLinkName(); - string fileMiddleLinkPath = Path.Join( - isMiddleLinkInVirtualDrive ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, - fileMiddleLinkFileName); - + string fileMiddleLinkPath = Path.Join(middleLinkExpectedFolderPath, fileMiddleLinkFileName); // Directory middle link string dirMiddleLinkFileName = GetRandomLinkName(); - string dirMiddleLinkPath = Path.Join( - isMiddleLinkInVirtualDrive ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, - dirMiddleLinkFileName); + string dirMiddleLinkPath = Path.Join(middleLinkExpectedFolderPath, dirMiddleLinkFileName); + string targetExpectedFolderPath = GetVirtualOrRealPath(isTargetInVirtualDrive); // File final target string fileFinalTargetFileName = GetRandomFileName(); - string fileFinalTargetPath = Path.Join( - isTargetInVirtualDrive ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, - fileFinalTargetFileName); - + string fileFinalTargetPath = Path.Join(targetExpectedFolderPath, fileFinalTargetFileName); // Directory final target string dirFinalTargetFileName = GetRandomDirName(); - string dirFinalTargetPath = Path.Join( - isTargetInVirtualDrive ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, - dirFinalTargetFileName); + string dirFinalTargetPath = Path.Join(targetExpectedFolderPath, dirFinalTargetFileName); // Create targets File.Create(fileFinalTargetPath).Dispose(); @@ -175,12 +157,14 @@ public void VirtualDrive_SymbolicLinks_WithIndirection(bool isFirstLinkInVirtual // Create initial links FileInfo fileLinkInfo = new FileInfo(fileLinkPath); fileLinkInfo.CreateAsSymbolicLink(fileMiddleLinkPath); + DirectoryInfo dirLinkInfo = new DirectoryInfo(dirLinkPath); dirLinkInfo.CreateAsSymbolicLink(dirMiddleLinkPath); // Create middle links FileInfo fileMiddleLinkInfo = new FileInfo(fileMiddleLinkPath); fileMiddleLinkInfo.CreateAsSymbolicLink(fileFinalTargetPath); + DirectoryInfo dirMiddleLinkInfo = new DirectoryInfo(dirMiddleLinkPath); dirMiddleLinkInfo.CreateAsSymbolicLink(dirFinalTargetPath); @@ -188,23 +172,21 @@ public void VirtualDrive_SymbolicLinks_WithIndirection(bool isFirstLinkInVirtual // LinkTarget always retrieves the immediate target, so the expected value // is always the path that was provided by the user for the middle link - string expectedFileTargetPath = fileMiddleLinkPath; - string expectedDirTargetPath = dirMiddleLinkPath; // Verify the LinkTarget values of the link infos - Assert.Equal(expectedFileTargetPath, fileLinkInfo.LinkTarget); - Assert.Equal(expectedDirTargetPath, dirLinkInfo.LinkTarget); + Assert.Equal(fileMiddleLinkPath, fileLinkInfo.LinkTarget); + Assert.Equal(dirMiddleLinkPath, dirLinkInfo.LinkTarget); // When the target is in a virtual drive, // the expected target path is the real path, not the virtual path // When returnFinalTarget is true, the expected target path is the // resolved path from the final target in the chain of links - string expectedTargetFileInfoFullName = Path.Join( - isExpectedTargetPathVirtual ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, + string expectedTargetPath = GetVirtualOrRealPath(isExpectedTargetPathVirtual); + + string expectedTargetFileInfoFullName = Path.Join(expectedTargetPath, returnFinalTarget ? fileFinalTargetFileName : fileMiddleLinkFileName); - string expectedTargetDirectoryInfoFullName = Path.Join( - isExpectedTargetPathVirtual ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir, + string expectedTargetDirectoryInfoFullName = Path.Join(expectedTargetPath, returnFinalTarget ? dirFinalTargetFileName : dirMiddleLinkFileName); // Verify target infos from link info instances @@ -228,6 +210,8 @@ public void VirtualDrive_SymbolicLinks_WithIndirection(bool isFirstLinkInVirtual Assert.Equal(expectedTargetDirectoryInfoFullName, targetFileInfoFromDirectory.FullName); } + private string GetVirtualOrRealPath(bool condition) => condition ? $"{VirtualDriveLetter}:" : VirtualDriveTargetDir; + // Temporary Windows directory that can be mounted to a drive letter using the subst command private string? _virtualDriveTargetDir = null; private string VirtualDriveTargetDir From be9e582750ff6298bda7d2c479320ef9372f3d7f Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Thu, 26 Aug 2021 17:23:45 -0700 Subject: [PATCH 16/19] Revert MountHelper.CreateSymbolicLink changes. Unrelated, and will be refactored/removed in the future. Detect if SUBST is available in Windows machine, to bring back Nano. --- .../TestUtilities/System/PlatformDetection.cs | 22 ++++++++++ .../PortedCommon/ReparsePointUtilities.cs | 40 ++++++++++++++----- .../VirtualDriveSymbolicLinks.Windows.cs | 2 +- 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index efef067dbce645..5bfd409b205499 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -267,6 +267,28 @@ private static bool GetStaticNonPublicBooleanPropertyValue(string typeName, stri public static bool IsIcuGlobalization => ICUVersion > new Version(0,0,0,0); public static bool IsNlsGlobalization => IsNotInvariantGlobalization && !IsIcuGlobalization; + public static bool IsSubstAvailable + { + get + { + try + { + if (OperatingSystem.IsWindows()) + { + string systemRoot = Environment.GetEnvironmentVariable("SystemRoot"); + if (string.IsNullOrWhiteSpace(systemRoot)) + { + return false; + } + string system32 = Path.Join(systemRoot, "System32"); + return File.Exists(Path.Join(system32, "subst.exe")); + } + } + catch { } + return false; + } + } + private static Version GetICUVersion() { int version = 0; diff --git a/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs b/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs index 25fa696a73c7c7..35ab9d03b222cf 100644 --- a/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs +++ b/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs @@ -31,20 +31,23 @@ public static class MountHelper /// Creates a symbolic link using command line tools. public static bool CreateSymbolicLink(string linkPath, string targetPath, bool isDirectory) { - string fileName; - string[] arguments; + Process symLinkProcess = new Process(); if (OperatingSystem.IsWindows()) { - fileName = "cmd"; - arguments = new[] { "/c", "mklink", isDirectory ? "/D" : "", linkPath, targetPath }; + symLinkProcess.StartInfo.FileName = "cmd"; + symLinkProcess.StartInfo.Arguments = string.Format("/c mklink{0} \"{1}\" \"{2}\"", isDirectory ? " /D" : "", linkPath, targetPath); } else { - fileName = "/bin/ln"; - arguments = new[] { "-s", targetPath, linkPath }; + symLinkProcess.StartInfo.FileName = "/bin/ln"; + symLinkProcess.StartInfo.Arguments = string.Format("-s \"{0}\" \"{1}\"", targetPath, linkPath); } + symLinkProcess.StartInfo.UseShellExecute = false; + symLinkProcess.StartInfo.RedirectStandardOutput = true; + symLinkProcess.Start(); - return RunProcess(CreateStartInfo(fileName, arguments)); + symLinkProcess.WaitForExit(); + return (0 == symLinkProcess.ExitCode); } /// On Windows, creates a junction using command line tools. @@ -55,7 +58,7 @@ public static bool CreateJunction(string junctionPath, string targetPath) throw new PlatformNotSupportedException(); } - return RunProcess(CreateStartInfo("cmd", "/c", "mklink", "/J", junctionPath, targetPath)); + return RunProcess(CreateProcessStartInfo("cmd", "/c", "mklink", "/J", junctionPath, targetPath)); } /// @@ -70,7 +73,7 @@ public static char CreateVirtualDrive(string targetDir) } char driveLetter = GetNextAvailableDriveLetter(); - bool success = RunProcess(CreateStartInfo("subst", $"{driveLetter}:", targetDir)); + bool success = RunProcess(CreateProcessStartInfo("cmd", "/c", SubstPath, $"{driveLetter}:", targetDir)); if (!success || !DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter)) { throw new InvalidOperationException($"Could not create virtual drive {driveLetter}: with subst"); @@ -106,7 +109,7 @@ public static void DeleteVirtualDrive(char driveLetter) throw new PlatformNotSupportedException(); } - bool success = RunProcess(CreateStartInfo("subst", "/d", $"{driveLetter}:")); + bool success = RunProcess(CreateProcessStartInfo("cmd", "/c", SubstPath, "/d", $"{driveLetter}:")); if (!success || DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter)) { throw new InvalidOperationException($"Could not delete virtual drive {driveLetter}: with subst"); @@ -146,7 +149,7 @@ public static void Unmount(string mountPoint) throw new Exception(string.Format("Win32 error: {0}", Marshal.GetLastPInvokeError())); } - private static ProcessStartInfo CreateStartInfo(string fileName, params string[] arguments) + private static ProcessStartInfo CreateProcessStartInfo(string fileName, params string[] arguments) { var info = new ProcessStartInfo { @@ -170,6 +173,21 @@ private static bool RunProcess(ProcessStartInfo startInfo) return process.ExitCode == 0; } + private static string SubstPath + { + get + { + if (!OperatingSystem.IsWindows()) + { + throw new PlatformNotSupportedException(); + } + + string systemRoot = Environment.GetEnvironmentVariable("SystemRoot") ?? @"C:\Windows"; + string system32 = Path.Join(systemRoot, "System32"); + return Path.Join(system32, "subst.exe"); + } + } + /// For standalone debugging help. Change Main0 to Main public static void Main0(string[] args) { diff --git a/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs b/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs index 0c4f245691c96d..d88425086b7024 100644 --- a/src/libraries/System.IO.FileSystem/tests/VirtualDriveSymbolicLinks.Windows.cs +++ b/src/libraries/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(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsSubstAvailable))] public class VirtualDrive_SymbolicLinks : BaseSymbolicLinks { protected override void Dispose(bool disposing) From d33bda3acac076f63a851e9b08aae3e37c0590e0 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Thu, 26 Aug 2021 17:34:26 -0700 Subject: [PATCH 17/19] Add dwReserved0 check for mount points in GetFinalLinkTarget. --- .../src/System/IO/FileSystem.Windows.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 c85c02b243fc92..d2bde47c3134b6 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 @@ -555,8 +555,9 @@ internal static void CreateSymbolicLink(string path, string pathToTarget, bool i // The file or directory is not a reparse point. if ((data.dwFileAttributes & (uint)FileAttributes.ReparsePoint) == 0 || - // Only symbolic links are supported at the moment. - (data.dwReserved0 & Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_SYMLINK) == 0) + // Only symbolic links and mount points are supported at the moment. + ((data.dwReserved0 & Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_SYMLINK) == 0 && + (data.dwReserved0 & Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT) == 0)) { return null; } From 244246bdb6529301a1f070ce59cf9902471c7d30 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Thu, 26 Aug 2021 17:39:20 -0700 Subject: [PATCH 18/19] Use Yoda we don't. --- .../tests/PortedCommon/ReparsePointUtilities.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs b/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs index 35ab9d03b222cf..8c7f3608d35ce2 100644 --- a/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs +++ b/src/libraries/System.IO.FileSystem/tests/PortedCommon/ReparsePointUtilities.cs @@ -47,7 +47,7 @@ public static bool CreateSymbolicLink(string linkPath, string targetPath, bool i symLinkProcess.Start(); symLinkProcess.WaitForExit(); - return (0 == symLinkProcess.ExitCode); + return symLinkProcess.ExitCode == 0; } /// On Windows, creates a junction using command line tools. From bd7e9cc330c0a54b3754090ee500a6691c7e9ad5 Mon Sep 17 00:00:00 2001 From: David Cantu Date: Thu, 26 Aug 2021 20:14:58 -0700 Subject: [PATCH 19/19] Fix CI issues --- .../Common/tests/TestUtilities/System/PlatformDetection.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index 5bfd409b205499..fbd5df9afdde75 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -273,15 +273,15 @@ public static bool IsSubstAvailable { try { - if (OperatingSystem.IsWindows()) + if (IsWindows) { string systemRoot = Environment.GetEnvironmentVariable("SystemRoot"); if (string.IsNullOrWhiteSpace(systemRoot)) { return false; } - string system32 = Path.Join(systemRoot, "System32"); - return File.Exists(Path.Join(system32, "subst.exe")); + string system32 = Path.Combine(systemRoot, "System32"); + return File.Exists(Path.Combine(system32, "subst.exe")); } } catch { }