Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add internal junction support to link APIs #57996

Merged
merged 19 commits into from
Aug 27, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
8e7069a
Add mount point support to link APIs.
carlossanlop Aug 24, 2021
11960b4
Add junction and virtual drive tests.
carlossanlop Aug 24, 2021
e59f2b2
Move PrintName comment outside of if else of reparseTag check.
carlossanlop Aug 24, 2021
2923c9e
Add Windows platform specific attribute to junction and virtual drive…
carlossanlop Aug 24, 2021
b6cf857
Revert FILE_NAME_OPENED to FILE_NAME_NORMALIZED
carlossanlop Aug 24, 2021
3ef33c2
Revert addition of FILE_NAME_OPENED const.
carlossanlop Aug 24, 2021
bc12ecf
Remove unnecessary enumeration junction test.
carlossanlop Aug 25, 2021
8957765
Rename GetNewCwdPath to ChangeCurrentDirectory
carlossanlop Aug 25, 2021
ce2aa86
Make Junction_ResolveLinkTarget a theory and test both resolveFinalTa…
carlossanlop Aug 25, 2021
999cddc
Shorter name for targetPath string. Typo in comment. Fix Debug.Assert.
carlossanlop Aug 25, 2021
42fb3a0
Clarify test comment. Change PlatformDetection for OperatingSystem ch…
carlossanlop Aug 25, 2021
ca1f2b2
Cleaner unit tests for virtual drive, add indirection test
carlossanlop Aug 26, 2021
6f6d368
Skip virtual drive tests in Windows Nano (subst not available). Small…
carlossanlop Aug 26, 2021
ec8000b
Simplify Junctions tests, add indirection test
carlossanlop Aug 26, 2021
7df549e
Address test suggestions.
carlossanlop Aug 26, 2021
be9e582
Revert MountHelper.CreateSymbolicLink changes. Unrelated, and will be…
carlossanlop Aug 27, 2021
d33bda3
Add dwReserved0 check for mount points in GetFinalLinkTarget.
carlossanlop Aug 27, 2021
244246b
Use Yoda we don't.
carlossanlop Aug 27, 2021
bd7e9cc
Fix CI issues
jozkee Aug 27, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member Author

Choose a reason for hiding this comment

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

I renamed this to match the names that were provided in the DUMMYUNIONNAME section of the official Windows docs.

Copy link
Member Author

Choose a reason for hiding this comment

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

{
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;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// 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);
carlossanlop marked this conversation as resolved.
Show resolved Hide resolved

protected abstract FileSystemInfo ResolveLinkTarget(string junctionPath, bool returnFinalTarget);

protected abstract void VerifyEnumerateMethods(string junctionPath, string[] expectedFiles, string[] expectedDirectories, string[] expectedEntries);
carlossanlop marked this conversation as resolved.
Show resolved Hide resolved

protected void VerifyEnumeration(IEnumerable<string> expectedEnumeration, IEnumerable<string> actualEnumeration)
{
foreach (string expectedItem in expectedEnumeration)
{
Assert.True(actualEnumeration.Contains(expectedItem));
}
carlossanlop marked this conversation as resolved.
Show resolved Hide resolved
}

[Fact]
public void Junction_ResolveLinkTarget()
{
string junctionPath = GetRandomLinkPath();
string targetPath = GetRandomDirPath();

CreateDirectory(targetPath);
DirectoryInfo junctionInfo = CreateJunction(junctionPath, targetPath);

FileSystemInfo? actualTargetInfo = ResolveLinkTarget(junctionPath, returnFinalTarget: false);
carlossanlop marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
carlossanlop marked this conversation as resolved.
Show resolved Hide resolved

// Create a dummy file or directory in cwd.
string fileOrDirectoryInCwd = GetRandomFileName();
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -36,5 +32,19 @@ protected DirectoryInfo CreateSelfReferencingSymbolicLink()
protected string GetRandomDirPath() => Path.Join(ActualTestDirectory.Value, GetRandomDirName());

private Lazy<string> ActualTestDirectory => new Lazy<string>(() => GetTestDirectoryActualCasing());

/// <summary>
/// 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.
Copy link
Member

Choose a reason for hiding this comment

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

is there a way to ensure that it's called from Remote Executor?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure if we can add such a check here.

Copy link
Member

Choose a reason for hiding this comment

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

it would have to be something like this
Debug.Assert(Environment.StackTrace.Contains("RemoteExecutor"));
or it would be reasonable to add a static flag in RemoteExecutor so you could just test RemoteExecutor.IsInRemoteExecutor.

/// </summary>
/// <returns>The path of the new cwd.</returns>
protected string GetNewCwdPath()
{
string tempCwd = GetRandomDirPath();
Directory.CreateDirectory(tempCwd);
Directory.SetCurrentDirectory(tempCwd);
return tempCwd;
}

carlossanlop marked this conversation as resolved.
Show resolved Hide resolved
}
}
46 changes: 46 additions & 0 deletions src/libraries/System.IO.FileSystem/tests/Directory/Junctions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// 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);

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);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// 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);

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);
}
}
}
Loading