Skip to content

Commit

Permalink
Handle symlinks embedding into binlogs - approach #2 (#8306)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
JanKrivanek authored Jan 18, 2023
1 parent de6a285 commit 98924b8
Show file tree
Hide file tree
Showing 3 changed files with 9 additions and 110 deletions.
8 changes: 7 additions & 1 deletion src/Build.UnitTests/BinaryLogger_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,13 @@ public void BinaryLoggerShouldEmbedSymlinkFilesViaTaskOutput()
string testFileName = "foobar.txt";
string symlinkName = "symlink1.txt";
string symlinkLvl2Name = "symlink2.txt";
string emptyFileName = "empty.txt";
TransientTestFolder testFolder = _env.DefaultTestDirectory.CreateDirectory("TestDir");
TransientTestFolder testFolder2 = _env.DefaultTestDirectory.CreateDirectory("TestDir2");
TransientTestFile testFile = testFolder.CreateFile(testFileName, string.Join(Environment.NewLine, new[] { "123", "456" }));
string symlinkPath = Path.Combine(testFolder2.Path, symlinkName);
string symlinkLvl2Path = Path.Combine(testFolder2.Path, symlinkLvl2Name);
string emptyFile = testFolder.CreateFile(emptyFileName).Path;

string errorMessage = string.Empty;
Assert.True(NativeMethodsShared.MakeSymbolicLink(symlinkPath, testFile.Path, ref errorMessage), errorMessage);
Expand Down Expand Up @@ -248,9 +250,12 @@ public void BinaryLoggerShouldEmbedSymlinkFilesViaTaskOutput()
<CreateItem Include=""{1}"">
<Output TaskParameter=""Include"" ItemName=""EmbedInBinlog"" />
</CreateItem>
<CreateItem Include=""{2}"">
<Output TaskParameter=""Include"" ItemName=""EmbedInBinlog"" />
</CreateItem>
</Target>
</Project>";
var testProject = string.Format(testProjectFmt, symlinkPath, symlinkLvl2Path);
var testProject = string.Format(testProjectFmt, symlinkPath, symlinkLvl2Path, emptyFile);
ObjectModelHelpers.BuildProjectExpectSuccess(testProject, binaryLogger);
var projectImportsZipPath = Path.ChangeExtension(_logFile, ".ProjectImports.zip");
using var fileStream = new FileStream(projectImportsZipPath, FileMode.Open);
Expand All @@ -261,6 +266,7 @@ public void BinaryLoggerShouldEmbedSymlinkFilesViaTaskOutput()
zipArchive.Entries.ShouldContain(zE => zE.Name.EndsWith("testtaskoutputfile.txt"));
zipArchive.Entries.ShouldContain(zE => zE.Name.EndsWith(symlinkName));
zipArchive.Entries.ShouldContain(zE => zE.Name.EndsWith(symlinkLvl2Name));
zipArchive.Entries.ShouldContain(zE => zE.Name.EndsWith(emptyFileName));
}

[Fact]
Expand Down
4 changes: 2 additions & 2 deletions src/Build/Logging/BinaryLogger/ProjectImportsCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ private void AddFileCore(string filePath)
return;
}

if (!NativeMethodsShared.ExistAndHasContent(filePath))
if (!File.Exists(filePath))
{
_processedFiles.Add(filePath);
return;
Expand All @@ -144,8 +144,8 @@ private void AddFileCore(string filePath)
return;
}

using Stream entryStream = OpenArchiveEntry(filePath);
using FileStream content = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete);
using Stream entryStream = OpenArchiveEntry(filePath);
content.CopyTo(entryStream);
}

Expand Down
107 changes: 0 additions & 107 deletions src/Framework/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1041,99 +1041,6 @@ internal static MemoryStatus GetMemoryStatus()
return null;
}

internal static bool ExistAndHasContent(string path)
{
var fileInfo = new FileInfo(path);

// File exist and has some content
return fileInfo.Exists &&
(fileInfo.Length > 0 ||
// Or final destination of the link is nonempty file
(
IsSymLink(fileInfo) &&
TryGetFinalLinkTarget(fileInfo, out string finalTarget, out _) &&
File.Exists(finalTarget) &&
new FileInfo(finalTarget).Length > 0
)
);
}

internal static bool IsSymLink(FileInfo fileInfo)
{
#if NET
return fileInfo.Exists && !string.IsNullOrEmpty(fileInfo.LinkTarget);
#else
if (!IsWindows)
{
return false;
}

WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA();

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;
#endif
}

internal static bool IsSymLink(string path)
{
return IsSymLink(new FileInfo(path));
}

internal static bool TryGetFinalLinkTarget(FileInfo fileInfo, out string finalTarget, out string errorMessage)
{
if (!IsWindows)
{
errorMessage = null;
#if NET
while(!string.IsNullOrEmpty(fileInfo.LinkTarget))
{
fileInfo = new FileInfo(fileInfo.LinkTarget);
}
finalTarget = fileInfo.FullName;
return true;
#else

finalTarget = null;
return false;
#endif
}

using SafeFileHandle handle = OpenFileThroughSymlinks(fileInfo.FullName);
if (handle.IsInvalid)
{
// Link is broken.
errorMessage = Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()).Message;
finalTarget = null;
return false;
}

const int initialBufferSize = 4096;
char[] targetPathBuffer = new char[initialBufferSize];
uint result = GetFinalPathNameByHandle(handle, targetPathBuffer);

// Buffer too small
if (result > targetPathBuffer.Length)
{
targetPathBuffer = new char[(int)result];
result = GetFinalPathNameByHandle(handle, targetPathBuffer);
}

// Error
if (result == 0)
{
errorMessage = Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()).Message;
finalTarget = null;
return false;
}

// Normalize \\?\ and \??\ syntax.
finalTarget = new string(targetPathBuffer, 0, (int)result).TrimStart(new char[] { '\\', '?' });
errorMessage = null;
return true;
}

internal static bool MakeSymbolicLink(string newFileName, string exitingFileName, ref string errorMessage)
{
bool symbolicLinkCreated;
Expand Down Expand Up @@ -1776,20 +1683,6 @@ out FILETIME lpLastWriteTime
[DllImport("libc", SetLastError = true)]
internal static extern int symlink(string oldpath, string newpath);

internal const uint FILE_NAME_NORMALIZED = 0x0;

[SupportedOSPlatform("windows")]
static uint GetFinalPathNameByHandle(SafeFileHandle fileHandle, char[] filePath) =>
GetFinalPathNameByHandle(fileHandle, filePath, (uint) filePath.Length, FILE_NAME_NORMALIZED);

[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
[SupportedOSPlatform("windows")]
static extern uint GetFinalPathNameByHandle(
SafeFileHandle hFile,
[Out] char[] lpszFilePath,
uint cchFilePath,
uint dwFlags);

#endregion

#region helper methods
Expand Down

0 comments on commit 98924b8

Please sign in to comment.