Skip to content

Conversation

@narasamdya
Copy link
Contributor

@narasamdya narasamdya commented Oct 16, 2021

In this PR we want to enable symlinks in ProjFS-Managed. This symlink feature is needed by our build system to properly treat symlinks used/produced during builds.


IF "%1"=="" (SET "Configuration=Debug") ELSE (SET "Configuration=%1")
IF "%2"=="" (SET "ProjFSManagedVersion=0.2.173.2") ELSE (SET "ProjFSManagedVersion=%2")
IF "%2"=="" (SET "ProjFSManagedVersion=1.2.19351.1") ELSE (SET "ProjFSManagedVersion=%2")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change was only for testing.

/// <c>true</c> if the entry was successfully added to the enumeration buffer, <c>false</c> otherwise.
/// </para>
/// </returns>
bool Add2(
Copy link
Contributor

@erikmav erikmav Oct 18, 2021

Choose a reason for hiding this comment

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

I recommend just 'Add', the additional param should differentiate the overloads. #Closed

<!-- The real version is determined by the official build process.
This version is a "built on dev box" special version. -->
<ProjFSManagedVersion>0.2.173.2</ProjFSManagedVersion>
<ProjFSManagedVersion>1.2.19351.1</ProjFSManagedVersion>
Copy link
Contributor

@erikmav erikmav Oct 18, 2021

Choose a reason for hiding this comment

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

Should revert this, version number is updated by Windows team CI pipeline #Closed

<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netcoreapp3.1|AnyCPU'">
Copy link
Contributor

@erikmav erikmav Oct 18, 2021

Choose a reason for hiding this comment

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

Seems like all these can be consolidated into an true in the main property list #Closed

Copy link
Contributor

Choose a reason for hiding this comment

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

(tag formatting fail) Consolidate to single property in the main PropertyGroup without all these conditions

System::DateTime changeTime,
System::String^ symlinkTargetOrNull) sealed
{
if (System::String::IsNullOrEmpty(fileName))
Copy link
Contributor

@erikmav erikmav Oct 18, 2021

Choose a reason for hiding this comment

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

Move all validation code common between original Add() and this overload into a helper method to avoid duplication #Closed

Copy link
Contributor

Choose a reason for hiding this comment

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

Note I do not recommend combining the 2 overloads into one method to avoid the data-dependent branch below for symlinkTargetOrNull

System::DateTime lastAccessTime,
System::DateTime lastWriteTime,
System::DateTime changeTime,
System::String^ symlinkTargetOrNull) sealed

Choose a reason for hiding this comment

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

I wonder if we want an arg for link type, so eventually we can add directory symlink, hard link, etc... And then error for now if anything but symlink

Copy link
Contributor

Choose a reason for hiding this comment

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

The underlying ProjFS API has an extension point for other link types but is not filled in. I would minimize the API size for now, as a 3rd overload can always be added to deal with that if it happens.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As of now, we can use ProjFS API for file and directory symlinks. For other types of reparse point, like junction, the extended info provided in ProjFS API doesn't have any field to specify that.

System::DateTime lastAccessTime,
System::DateTime lastWriteTime,
System::DateTime changeTime,
System::String^ symlinkTargetOrNull) sealed

Choose a reason for hiding this comment

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

System::String^

what is the reason for some of these being pass by reference and not others?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is it not the others are primitive types or structs? So pass-by-value is more appropriate, unless you need to modify them.

Copy link
Contributor

Choose a reason for hiding this comment

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

String instances in managed C++ are always refs since String is a class. The "by ref" here is passing the pointer to the heap String.

public sealed class FileSystemApi
{
[Flags]
public enum SymbolicLinkTarget : uint

Choose a reason for hiding this comment

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

SymbolicLinkTarget

Seems like the name here doesn't describe the vales. SymbolicLinkOptions?


namespace SimpleProviderManaged
{
public sealed class FileSystemApi

Choose a reason for hiding this comment

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

FileSystemApi

Was this code copied from somewhere? If so, should there be a central locaiton to use it from?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it resembles some code in BuildXL but not sure if we can consume it from there.
https://github.com/microsoft/BuildXL/blob/master/Public/Src/Utilities/Native/IO/Windows/FileSystem.Win.cs

@narasamdya is it correct? I guess I could trim it down a bit (remove enum entries not used in SimpleProvider) so it becomes just a file helper class for SimpleProvider. I'm not sure if we should take a dependency on BuildXL repo from this repo here

Copy link
Contributor Author

@narasamdya narasamdya Oct 25, 2021

Choose a reason for hiding this comment

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

I cherry-picked it from BuildXL.

And, no, don't take any dependency from BuildXL here.

Copy link

@erickulcyk erickulcyk left a comment

Choose a reason for hiding this comment

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

:shipit:

Assert.That("RandomNonsense", Is.Not.EqualTo(line));
}

// We start the virtualization instance in each test case, so that exercises the following
Copy link
Contributor Author

Choose a reason for hiding this comment

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

/

May be add the following kind of test that involve both file and directory symlink:

 ├── Dir.lnk->Dir
 ├── Dir
 │   ├── file.lnk -> file.txt
 │   └── file.txt
 └── file.lnk -> Dir.lnk\file.lnk

Accessing file.lnk should result in Dir\file.txt.

{
public ProjectedFileInfo(
string name,
string fullName,
Copy link
Contributor Author

@narasamdya narasamdya Oct 25, 2021

Choose a reason for hiding this comment

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

fullName

Why do we need a fullName here?

We should be able to get the full path from the layerPath.

Copy link
Contributor

@yerudako yerudako Oct 25, 2021

Choose a reason for hiding this comment

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

Name only contains the file name (missing the subpath relative to the layer root). We need to have either that subpath or the full path in order to pass them to TryGetTargetIfReparsePoint. I could also calculate and use that relative path (in the places where ProjectedFileInfo is initialized) instead of this property if that makes more sense?

}
else if (Path.IsPathRooted(targetPath))
{
string targetRelativePath = FileSystemApi.TryGetPathRelativeToRoot(this.layerRoot, targetPath, fileInfo.IsDirectory);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

string targetRelativePath = FileSystemApi.TryGetPathRelativeToRoot(this.layerRoot, targetPath, fileInfo.IsDirectory);

Why do we need to get the relative path to root?

Do we have tests with rooted path target?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, both of the existing symlink tests exercise this code path. I could also add more tests with un-rooted target paths (by updating Helpers.CreateVirtualSymlink).

@narasamdya narasamdya marked this pull request as ready for review November 4, 2021 17:04
@narasamdya narasamdya changed the title [DRAFT] Enable symlink in ProjFS-Managed Enable symlink in ProjFS-Managed Nov 4, 2021
@yerudako
Copy link
Contributor

Status update - We are currently trying to see if we need a pool update or whether we have to add conditional compilation checks to make it build on the old build agent.

pin_ptr<const WCHAR> targetPath = PtrToStringChars(symlinkTargetOrNull);
extendedInfo.Symlink.TargetName = targetPath;

hr = ::PrjFillDirEntryBuffer2(m_dirEntryBufferHandle,
Copy link
Contributor

Choose a reason for hiding this comment

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

This now results in a linker error. Your header that defines the signatures and structures for the new APIs lets this compile. But this isn't being called through a dynamic import and ProjectedFSLib.Managed.props still references C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0 in several places instead of 19041.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks! I reverted the conditional changes for now (in case this will work with a new agent pool). I also updated the props as you suggested. What is interesting, my Visual Studio is showing that it uses 10.0.22000.0
image

Copy link
Contributor

Choose a reason for hiding this comment

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

Except when I'm dealing with this project (which is not often), I don't use Visual Studio as anything other than a nice editor; everything I work on is in the OS repo and built with sources files and Razzle commands. So I don't know enough about how VS works to explain why it would show 22000 in your UI but 18362 in the props file.

Copy link
Contributor

Choose a reason for hiding this comment

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

SDK version 22000 is the Win11 SDK. My bet is that the worker pool you're using has moved to VS2022

Copy link
Contributor

Choose a reason for hiding this comment

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

(Or really your local VS is 2022 with the Win11 SDK installed as default)

@yerudako
Copy link
Contributor

Another status update - in order to not mix too many changes into this PR, I opened #77 to track the build agent upgrade. I believe that unfortunately this PR will have to wait until the build agent runs with the new OS.

this->_PrjMarkDirectoryAsPlaceholder = reinterpret_cast<t_PrjMarkDirectoryAsPlaceholder>(::GetProcAddress(projFsLib,
"PrjMarkDirectoryAsPlaceholder"));
bool environmentSupportsSymlinks = false;
if (::GetProcAddress(projFsLib, "PrjWritePlaceholderInfo2") != nullptr)
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar treatment needed for PrjFillDirEntryBuffer2. Then DirectoryEnumerationResults::Add needs to call the dynamically loaded version.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ohhhh good catch!! I didn't notice that. Thank you Chris!

Copy link
Contributor

Choose a reason for hiding this comment

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

That might be the reason providers don't run on 2019. Let me try that.

Copy link
Contributor

Choose a reason for hiding this comment

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

I added conditional code to the enumeration, seems to work - although I am not 100% I did it the best way (not sure if I should call LoadLibrary there). Christian - could you please take a look and let me know what you think and if there's a better way to do that I will be happy to use that.

Copy link
Contributor

Choose a reason for hiding this comment

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

I have some changes that consolidate the conditional API use into the ApiHelper object, and that fix the bug in SimpleProvider that was the actual cause of the InsufficientBuffer error on .NET Framework but not .NET Core. I don't have permission to push to your branch though.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh sorry for that - I am not sure why GitHub does not allow it - I can merge your branch into this branch or cherry-pick the commit if you could send their hashes? Thanks Christian!

Copy link
Contributor Author

@narasamdya narasamdya Dec 6, 2021

Choose a reason for hiding this comment

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

@cgallred, do you know why, with the current implementation of SimpleProvider, TestEnumerationInVirtualizationRoot that includes directory enumeration, but not symlinks, passes in both .NET core and framework?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd guess that the case doesn't hit the problem scenario. It may be able to fit all the enumeration entries in a single buffer, so it doesn't provoke a repeat call to the callback. Enumeration that includes symlinks takes up more space in the enumeration buffer.

@cgallred cgallred merged commit 5d047db into microsoft:main Dec 6, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants