зеркало из https://github.com/microsoft/BuildXL.git
Use xattrs to mark shared opaque outputs on Mac (#1007)
The implementation for Windows remains the same and continues to use magic timestamps. On Mac, instead of timestamps extended attributes are used. Concretely, a file is a shared opaque output IFF it has a com.microsoft.buildxl:shared_opaque_output attribute and it's value is equal to 42 (at some point in the future we could insert a more meaningful value here, e.g., PipId) AB#1607996
This commit is contained in:
Родитель
46091c1b57
Коммит
7e32af8aa6
|
@ -21,7 +21,8 @@ readonly FULLY_CACHED=0
|
|||
readonly NOT_FULLY_CACHED=1
|
||||
|
||||
# this is the magic timestamp ("2003-03-03 3:03:03") translated to UTC Epoch seconds
|
||||
readonly magicTimestamp=$(date -j -u -f "%Y-%m-%d %H:%M:%S" "2003-03-03 3:03:03" +%s)
|
||||
# readonly magicTimestamp=$(date -j -u -f "%Y-%m-%d %H:%M:%S" "2003-03-03 3:03:03" +%s)
|
||||
readonly magicXattrName="com.microsoft.buildxl:shared_opaque_output"
|
||||
|
||||
function run_build_and_check_stuff {
|
||||
local expectGraphReloadedStatus=$1
|
||||
|
@ -58,16 +59,15 @@ function run_build_and_check_stuff {
|
|||
print_info $(if [[ $expectFullyCachedStatus == $FULLY_CACHED ]]; then echo "Verified build fully cached"; else echo "Verified build NOT fully cached"; fi)
|
||||
fi
|
||||
|
||||
# check that all files in all shared opaque directories (whose name is 'sod*') have the magic timestamp for 'Btime'
|
||||
# check that all files in all shared opaque directories (whose name is 'sod*') have the magic xattr
|
||||
local sodFilesFile="$MY_DIR/sod-files.txt"
|
||||
find "$MY_DIR/out/objects" -type d -name 'sod*' -exec find {} -type f -o -type l \; > "$sodFilesFile"
|
||||
local sodFilesTimestamps=$(cat "$sodFilesFile" | xargs stat -t "%s" -f "%SB" | sort | uniq)
|
||||
if [[ $sodFilesTimestamps != $magicTimestamp ]]; then
|
||||
print_error "Some files in the some shared output directories don't have the magic timestamp ('2003-03-03 3:03:03', i.e., $magicTimestamp) for Btime"
|
||||
cat "$sodFilesFile" | xargs stat -t "%s" -f "Btime: %SB, path: %N"
|
||||
rm -f "$sodFilesFile"
|
||||
return 4
|
||||
fi
|
||||
for f in `cat $sodFilesFile`; do
|
||||
xattr -s "$f" | grep -q $magicXattrName || {
|
||||
print_error "File '$f' does not have the magic xattr '$magicXattrName'"
|
||||
return 4
|
||||
}
|
||||
done
|
||||
rm -f "$sodFilesFile"
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.AccessControl;
|
||||
using BuildXL.Native.IO;
|
||||
using BuildXL.Utilities;
|
||||
|
@ -10,31 +11,152 @@ using static BuildXL.Utilities.FormattableStringEx;
|
|||
namespace BuildXL.Processes
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class for identifying a file as being an output of a shared opaque. This helps the scrubber to be more precautious when deleting files under shared opaques.
|
||||
/// Utility class for identifying a file as being an output of a shared opaque.
|
||||
/// This helps the scrubber to be more precautious when deleting files under shared opaques.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Currently, we use two different strategies when running on Windows and non-Windows platforms
|
||||
/// - on Windows, we set a magic timestamp as file's creation (birth) date
|
||||
/// - on Mac, we set an extended attribute with a special name.
|
||||
/// On Mac, some tools tend to change the timestamps (even the birth date) which is the reason for this.
|
||||
///
|
||||
/// TODO: eventually we should unify this and use the same mechanism for all platforms, if possible.
|
||||
/// </remarks>
|
||||
public static class SharedOpaqueOutputHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Flags the given path as being an output under a shared opaque by setting the creation time to <see cref="WellKnownTimestamps.OutputInSharedOpaqueTimestamp"/>
|
||||
/// </summary>
|
||||
/// <exception cref="BuildXLException">When the timestamp cannot be set</exception>
|
||||
public static void SetPathAsSharedOpaqueOutput(string expandedPath)
|
||||
private static class Win
|
||||
{
|
||||
try
|
||||
/// <summary>
|
||||
/// Flags the given path as being an output under a shared opaque by setting the creation time to
|
||||
/// <see cref="WellKnownTimestamps.OutputInSharedOpaqueTimestamp"/>.
|
||||
/// </summary>
|
||||
/// <exception cref="BuildXLException">When the timestamp cannot be set</exception>
|
||||
public static void SetPathAsSharedOpaqueOutput(string expandedPath)
|
||||
{
|
||||
// Only the creation time is used to identify a file as the output of a shared opaque
|
||||
FileUtilities.SetFileTimestamps(expandedPath, new FileTimestamps(WellKnownTimestamps.OutputInSharedOpaqueTimestamp));
|
||||
}
|
||||
catch (BuildXLException ex)
|
||||
{
|
||||
// On (unsafe) double writes, a race can occur, we give the other contender a second chance
|
||||
if (IsSharedOpaqueOutput(expandedPath))
|
||||
// In the case of a no replay, this case can happen if the file got into the cache as a static output,
|
||||
// but later was made a shared opaque output without a content change.
|
||||
// Make sure we allow for attribute writing first
|
||||
var writeAttributesDenied = !FileUtilities.HasWritableAttributeAccessControl(expandedPath);
|
||||
if (writeAttributesDenied)
|
||||
{
|
||||
return;
|
||||
FileUtilities.SetFileAccessControl(expandedPath, FileSystemRights.WriteAttributes | FileSystemRights.WriteExtendedAttributes, allow: true);
|
||||
}
|
||||
|
||||
// Since these files should be just created outputs, this shouldn't happen and we bail out hard.
|
||||
throw new BuildXLException(I($"Failed to open output file '{expandedPath}' for writing."), ex);
|
||||
try
|
||||
{
|
||||
// Only the creation time is used to identify a file as the output of a shared opaque
|
||||
FileUtilities.SetFileTimestamps(expandedPath, new FileTimestamps(WellKnownTimestamps.OutputInSharedOpaqueTimestamp));
|
||||
}
|
||||
catch (BuildXLException ex)
|
||||
{
|
||||
// On (unsafe) double writes, a race can occur, we give the other contender a second chance
|
||||
if (IsSharedOpaqueOutput(expandedPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Since these files should be just created outputs, this shouldn't happen and we bail out hard.
|
||||
throw new BuildXLException(I($"Failed to open output file '{expandedPath}' for writing."), ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Restore the attributes as they were originally set
|
||||
if (writeAttributesDenied)
|
||||
{
|
||||
FileUtilities.SetFileAccessControl(expandedPath, FileSystemRights.WriteAttributes | FileSystemRights.WriteExtendedAttributes, allow: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given path is an output under a shared opaque by verifying whether <see cref="WellKnownTimestamps.OutputInSharedOpaqueTimestamp"/> is the creation time of the file
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the given path is a directory, it is always considered part of a shared opaque
|
||||
/// </remarks>
|
||||
public static bool IsSharedOpaqueOutput(string expandedPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var creationTime = FileUtilities.GetFileTimestamps(expandedPath).CreationTime;
|
||||
return creationTime == WellKnownTimestamps.OutputInSharedOpaqueTimestamp;
|
||||
}
|
||||
catch (BuildXLException ex)
|
||||
{
|
||||
throw new BuildXLException(I($"Failed to open output file '{expandedPath}' for reading."), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe class Unix
|
||||
{
|
||||
private const string MY_XATTR_NAME = "com.microsoft.buildxl:shared_opaque_output";
|
||||
|
||||
// arbitrary value; in the future, we could store something more useful here (e.g., the producer PipId or something)
|
||||
private const long MY_XATTR_VALUE = 42;
|
||||
|
||||
// from xattr.h:
|
||||
// #define XATTR_NOFOLLOW 0x0001 /* Don't follow symbolic links */
|
||||
private const int XATTR_NOFOLLOW = 1;
|
||||
|
||||
[DllImport("libc", EntryPoint = "setxattr")]
|
||||
private static extern int SetXattr(
|
||||
[MarshalAs(UnmanagedType.LPStr)] string path,
|
||||
[MarshalAs(UnmanagedType.LPStr)] string name,
|
||||
void *value,
|
||||
ulong size,
|
||||
uint position,
|
||||
int options);
|
||||
|
||||
[DllImport("libc", EntryPoint = "getxattr")]
|
||||
private static extern long GetXattr(
|
||||
[MarshalAs(UnmanagedType.LPStr)] string path,
|
||||
[MarshalAs(UnmanagedType.LPStr)] string name,
|
||||
void *value,
|
||||
ulong size,
|
||||
uint position,
|
||||
int options);
|
||||
|
||||
/// <summary>
|
||||
/// Flags the given path as being an output under a shared opaque by setting
|
||||
/// <see cref="MY_XATTR_NAME"/> xattr to a <see cref="MY_XATTR_VALUE"/>.
|
||||
/// </summary>
|
||||
public static void SetPathAsSharedOpaqueOutput(string expandedPath)
|
||||
{
|
||||
long value = MY_XATTR_VALUE;
|
||||
var err = SetXattr(expandedPath, MY_XATTR_NAME, &value, sizeof(long), 0, XATTR_NOFOLLOW);
|
||||
if (err != 0)
|
||||
{
|
||||
throw new BuildXLException(I($"Failed to set '{MY_XATTR_NAME}' extended attribute. Error: {err}"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the given path is an output under a shared opaque by checking if
|
||||
/// it contains extended attribute by <see cref="MY_XATTR_NAME"/> name.
|
||||
/// </summary>
|
||||
public static bool IsSharedOpaqueOutput(string expandedPath)
|
||||
{
|
||||
long value = 0;
|
||||
uint valueSize = sizeof(long);
|
||||
var resultSize = GetXattr(expandedPath, MY_XATTR_NAME, &value, valueSize, 0, XATTR_NOFOLLOW);
|
||||
return resultSize == valueSize && value == MY_XATTR_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks a given path as "shared opaque output"
|
||||
/// </summary>
|
||||
/// <exception cref="BuildXLException">When unsuccessful</exception>
|
||||
public static void SetPathAsSharedOpaqueOutput(string expandedPath)
|
||||
{
|
||||
if (OperatingSystemHelper.IsUnixOS)
|
||||
{
|
||||
Unix.SetPathAsSharedOpaqueOutput(expandedPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
Win.SetPathAsSharedOpaqueOutput(expandedPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,18 +196,9 @@ namespace BuildXL.Processes
|
|||
return true;
|
||||
}
|
||||
|
||||
DateTime creationTime;
|
||||
|
||||
try
|
||||
{
|
||||
creationTime = FileUtilities.GetFileTimestamps(expandedPath).CreationTime;
|
||||
}
|
||||
catch (BuildXLException ex)
|
||||
{
|
||||
throw new BuildXLException(I($"Failed to open output file '{expandedPath}' for reading."), ex);
|
||||
}
|
||||
|
||||
return creationTime == WellKnownTimestamps.OutputInSharedOpaqueTimestamp;
|
||||
return OperatingSystemHelper.IsUnixOS
|
||||
? Unix.IsSharedOpaqueOutput(expandedPath)
|
||||
: Win.IsSharedOpaqueOutput(expandedPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -93,37 +206,13 @@ namespace BuildXL.Processes
|
|||
/// </summary>
|
||||
public static void EnforceFileIsSharedOpaqueOutput(string expandedPath)
|
||||
{
|
||||
// If the file has the right timestamps already, then there is nothing to do.
|
||||
// If the file is already marked, then there is nothing to do.
|
||||
if (IsSharedOpaqueOutput(expandedPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// In the case of a no replay, this case can happen if the file got into the cache as a static output, but later was made a shared opaque
|
||||
// output without a content change.
|
||||
|
||||
#if PLATFORM_WIN
|
||||
// Make sure we allow for attribute writing first
|
||||
var writeAttributesDenied = !FileUtilities.HasWritableAttributeAccessControl(expandedPath);
|
||||
if (writeAttributesDenied)
|
||||
{
|
||||
FileUtilities.SetFileAccessControl(expandedPath, FileSystemRights.WriteAttributes | FileSystemRights.WriteExtendedAttributes, allow: true);
|
||||
}
|
||||
#endif
|
||||
try
|
||||
{
|
||||
SetPathAsSharedOpaqueOutput(expandedPath);
|
||||
}
|
||||
finally
|
||||
{
|
||||
#if PLATFORM_WIN
|
||||
// Restore the attributes as they were originally set
|
||||
if (writeAttributesDenied)
|
||||
{
|
||||
FileUtilities.SetFileAccessControl(expandedPath, FileSystemRights.WriteAttributes | FileSystemRights.WriteExtendedAttributes, allow: false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
SetPathAsSharedOpaqueOutput(expandedPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -171,7 +171,7 @@ namespace Test.BuildXL.Engine
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void OutputsUnderSharedOpaqueHaveAWellKnownCreationTimeEvenOnCacheReplay()
|
||||
public void OutputsUnderSharedOpaqueAreProperlyMarkedEvenOnCacheReplay()
|
||||
{
|
||||
var file = X("out/SharedOpaqueOutput.txt");
|
||||
var spec0 = ProduceFileUnderSharedOpaque(file);
|
||||
|
@ -185,8 +185,8 @@ namespace Test.BuildXL.Engine
|
|||
// Make sure the file was produced
|
||||
Assert.True(File.Exists(producedFile));
|
||||
|
||||
// And that it has a well-known creation time
|
||||
XAssert.AreEqual(WellKnownTimestamps.OutputInSharedOpaqueTimestamp, FileUtilities.GetFileTimestamps(producedFile).CreationTime);
|
||||
// And that it has been marked as shared opaque output
|
||||
XAssert.IsTrue(SharedOpaqueOutputHelper.IsSharedOpaqueOutput(producedFile));
|
||||
|
||||
File.Delete(producedFile);
|
||||
|
||||
|
@ -196,12 +196,12 @@ namespace Test.BuildXL.Engine
|
|||
IgnoreWarnings();
|
||||
// Make sure this is a cache replay
|
||||
AssertVerboseEventLogged(EventId.ProcessPipCacheHit);
|
||||
// And check the timestamp again
|
||||
XAssert.AreEqual(WellKnownTimestamps.OutputInSharedOpaqueTimestamp, FileUtilities.GetFileTimestamps(producedFile).CreationTime);
|
||||
// And check again that the file is still properly marked
|
||||
XAssert.IsTrue(SharedOpaqueOutputHelper.IsSharedOpaqueOutput(producedFile));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StaticOutputBecomingASharedOpaqueOutputHasWellKnownCreationTime()
|
||||
public void StaticOutputBecomingASharedOpaqueOutputIsProperlyMarkedAsSharedOpaqueOutput()
|
||||
{
|
||||
var file = X("out/MyFile.txt");
|
||||
|
||||
|
@ -216,8 +216,8 @@ namespace Test.BuildXL.Engine
|
|||
// Make sure the file was produced
|
||||
Assert.True(File.Exists(producedFile));
|
||||
|
||||
// Since this is a statically declared file, the creation timestamp should not be the one used for shared opaques
|
||||
XAssert.AreNotEqual(WellKnownTimestamps.OutputInSharedOpaqueTimestamp, FileUtilities.GetFileTimestamps(producedFile).CreationTime);
|
||||
// Since this is a statically declared file, it shouldn't be marked as a shared opaque output
|
||||
XAssert.IsFalse(SharedOpaqueOutputHelper.IsSharedOpaqueOutput(producedFile), "Statically declared file marked as shared opaque output");
|
||||
|
||||
// Delete the created file (since scrubbing is not on for this test, we have to simulate it)
|
||||
File.Delete(producedFile);
|
||||
|
@ -230,7 +230,7 @@ namespace Test.BuildXL.Engine
|
|||
RunEngine(rememberAllChangedTrackedInputs: true);
|
||||
|
||||
// Check the timestamp is the right one
|
||||
XAssert.AreEqual(WellKnownTimestamps.OutputInSharedOpaqueTimestamp, FileUtilities.GetFileTimestamps(producedFile).CreationTime);
|
||||
XAssert.IsTrue(SharedOpaqueOutputHelper.IsSharedOpaqueOutput(producedFile), "SOD file not marked on cache miss");
|
||||
|
||||
// Delete the file
|
||||
File.Delete(producedFile);
|
||||
|
@ -242,11 +242,11 @@ namespace Test.BuildXL.Engine
|
|||
// Make sure this is a cache replay
|
||||
AssertVerboseEventLogged(EventId.ProcessPipCacheHit);
|
||||
// Check the timestamp is the right one now
|
||||
XAssert.AreEqual(WellKnownTimestamps.OutputInSharedOpaqueTimestamp, FileUtilities.GetFileTimestamps(producedFile).CreationTime);
|
||||
XAssert.IsTrue(SharedOpaqueOutputHelper.IsSharedOpaqueOutput(producedFile), "SOD file not marked on cache replay");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SharedOpaqueOutputsOnFailingPipHaveWellKnownCreationTime()
|
||||
public void SharedOpaqueOutputsOnFailingPipMustBeProperlyMarked()
|
||||
{
|
||||
var file = X("out/MyFile.txt");
|
||||
var objDir = Configuration.Layout.ObjectDirectory.ToString(Context.PathTable);
|
||||
|
@ -261,7 +261,7 @@ namespace Test.BuildXL.Engine
|
|||
AssertErrorEventLogged(EventId.PipProcessError);
|
||||
|
||||
// Check the timestamp is the right one
|
||||
XAssert.AreEqual(WellKnownTimestamps.OutputInSharedOpaqueTimestamp, FileUtilities.GetFileTimestamps(producedFile).CreationTime);
|
||||
XAssert.IsTrue(SharedOpaqueOutputHelper.IsSharedOpaqueOutput(producedFile), "SOD file not marked on pip failure");
|
||||
}
|
||||
|
||||
private string ProduceFileUnderSharedOpaque(string file, bool failOnExit = false, string dependencies = "") => ProduceFileUnderDirectory(file, isDynamic: true, failOnExit, dependencies);
|
||||
|
|
Загрузка…
Ссылка в новой задаче