-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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 support for symlink files embedding to binlog #8213
Conversation
src/Framework/NativeMethods.cs
Outdated
|
||
WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA(); | ||
|
||
return NativeMethods.GetFileAttributesEx(fullPath, 0, ref data) && |
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.
Can this be simplified? I think you can check the reparse point on any framework and platform, and it's just as fast, right? Like you should be able to do this everywhere:
(File.GetAttributes(destFile) & FileAttributes.ReparsePoint) != 0
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 FileInfo
takes care about proper handling the logic on other platforms (e.g. https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Unix.cs#L653). So I believe simplification is not possible without impact on functionality.
Please let me know if I misunderstood.
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.
But - there can be another simplification
I realized and verified that FileStream
ctor already does the proper 'see through' thing with the symlinks.
So the change can be limited to just verifying that FileInfo
has nonzero content or it's a symlink.... Updated.
src/Framework/NativeMethods.cs
Outdated
|
||
if (IsSymLink(filePath)) | ||
{ | ||
using SafeFileHandle handle = OpenFileThroughSymlinks(filePath); |
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.
For these sorts of errors, you should be able to add [SupportedOSPlatform("windows")]
to the relevant methods.
4ab113f
to
870155c
Compare
@@ -25,6 +25,10 @@ To run the unit tests from Visual Studio: | |||
|
|||
To build MSBuild and run all unit tests from the command line, use `.\build.cmd -test`. | |||
|
|||
Some tests are creating symlinks to test associated functionality - in order for them to succeed you have two options: | |||
* Run those tests elevated |
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 there still a test that fails if you run elevated then run not-elevated? If so, might want to mention that and how to fix it.
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.
Creating symlinks require elevation or developer mode on each run.
Would you suggest different wording informing about this behavior?
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 put the option to enable developer mode first, and I'd add a warning for running tests elevated that if you then run not elevated, you have to do the following steps (and list out any tests that fail with running elevated --> not elevated along with an explanation of how to fix it—I think there was just one, and it could be fixed by deleting some directory...or you could fix the test to clean up properly.)
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.
Switched the order of options.
I'm not aware of issues with tests when switching elevation mode. The newly added test - BinaryLoggerShouldEmbedSymlinkFilesViaTaskOutput
- uses transient files. The pre-existing test that uses symlinks - DoNotFollowRecursiveSymlinks
cleans-up after itself as well.
Am I missing any other case?
If we have any - I'd definitely prefer to do a proper cleanup in tests rather than scare reader by extra steps in wiki. Please point me :-)
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.
@Forgind I've run all tests elevated and then without elevation and I do not observe any issues. Did you have any specific test or code issue in mind?
If not - do you feel there is any further improvement that can be made to the touched code or doc?
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.
TestCache2 was the one I had in mind that had failed recently due to admin --> not admin running of the test. I believe it specifically failed if a particular folder did not exist prior to the test's execution, then you ran the test as an admin, then you ran the test as a normal user. It's possible that's been fixed since that came up, though; it was a while ago.
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 seems to be addressed here: #8122
Do you feel there are any further improvement that can be made in scope of this PR?
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.
Yup; I forgot about that. That's all I can think of.
31c2402
to
b94eadd
Compare
|
||
return NativeMethods.GetFileAttributesEx(fileInfo.FullName, 0, ref data) && | ||
(data.fileAttributes & NativeMethods.FILE_ATTRIBUTE_DIRECTORY) == 0 && | ||
(data.fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT; |
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 could incorrectly return true for a file that is a reparse point but not a symbolic link.
One could use either GetFileInformationByHandleEx FILE_ATTRIBUTE_TAG_INFO or DeviceIoControl FSCTL_GET_REPARSE_POINT for reading the attributes and the reparse tag, and then compare to IO_REPARSE_TAG_SYMLINK; PowerShell uses FSCTL_GET_REPARSE_POINT. However, these functions would require first opening the file.
To get the file attributes and reparse tag without opening the file, one can use either FindFirstFileW or, starting from Windows 10 version 1709, NtQueryInformationByName FILE_STAT_INFORMATION. I assume that NtQueryInformationByName would be the most efficient of these, because it doesn't require opening and closing a handle to the parent directory.
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.
@KalleOlaviNiemitalo Thank you for pointing to this and for the detailed suggestion!
I'm thinking about keeping the attribute check to filter out 'normal files' scenarios and in case of detecting reparse point querying further (like by obtaining reparse tag via DeviceIoControl - similarly how this is done in runtime: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs#L574-L609).
The FindFirstFile
seems to be very neat way to get this info in single shot. This feels unnecessary heavy though. I haven't done any measuring 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.
The methods in PowerShell and .NET Runtime use FSCTL_GET_REPARSE_POINT which also retrieves the target string of the symbolic link. If you don't need that, I suspect GetFileInformationByHandleEx FILE_ATTRIBUTE_TAG_INFO could be lighter weight as it could avoid the string copy.
else | ||
{ | ||
symbolicLinkCreated = symlink(exitingFileName, newFileName) == 0; | ||
errorMessage = symbolicLinkCreated ? null : "The link() library call failed with the following error code: " + Marshal.GetLastWin32Error(); |
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.
It's the symlink
call, not the link
call. Not a new bug, 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.
Very valid. Linked this to a PR taking care of localizing the message
Fixes #6773 Context Supersedes #8213 and #8282 Symlinked files were not embedded into binlog - previous solution was too much focused on symlinks and hence requried nontrivial code to properly distinguish aymlinks with available contnet. Alternate approach - proceed with adding file as soon as it has available content Testing Preexisting unit test is excercising the scenario. Added a case for empty file - that still should not be added.
Fixes #6773
Context
Symlinked files were not embedded into binlog
Changes Made
Add detection of symlinked files and switch to their target
Support not added for full FW
Testing
Unit test added, attempting to embed symlinked file (failing without the fix)