-
Notifications
You must be signed in to change notification settings - Fork 38
Enable symlinks in ProjFS-Managed #73
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
Enable symlinks in ProjFS-Managed #73
Conversation
scripts/BuildProjFS-Managed.bat
Outdated
|
|
||
| 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") |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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
ProjectedFSLib.Managed.props
Outdated
| <!-- 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> |
There was a problem hiding this comment.
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'"> |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
|
||
| namespace SimpleProviderManaged | ||
| { | ||
| public sealed class FileSystemApi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
erickulcyk
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
![]()
| Assert.That("RandomNonsense", Is.Not.EqualTo(line)); | ||
| } | ||
|
|
||
| // We start the virtualization instance in each test case, so that exercises the following |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| { | ||
| public ProjectedFileInfo( | ||
| string name, | ||
| string fullName, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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).
|
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, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
…and adding conditional symbol declarations." This reverts commit 4f1ff1f.
|
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. |
…nfo2Wrapper # Conflicts: # scripts/BuildProjFS-Managed.bat
This reverts commit f71b4d2.
| this->_PrjMarkDirectoryAsPlaceholder = reinterpret_cast<t_PrjMarkDirectoryAsPlaceholder>(::GetProcAddress(projFsLib, | ||
| "PrjMarkDirectoryAsPlaceholder")); | ||
| bool environmentSupportsSymlinks = false; | ||
| if (::GetProcAddress(projFsLib, "PrjWritePlaceholderInfo2") != nullptr) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
Signed-off-by: yerudako <yerudako@gmail.com>
This reverts commit 6d632b5.

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.