File: InprocTrackingNativeMethods.cs
Web Access
Project: ..\..\..\src\Utilities\Microsoft.Build.Utilities.csproj (Microsoft.Build.Utilities.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#if FEATURE_FILE_TRACKER
 
using System;
using System.IO;
using System.Runtime.InteropServices;
#if FEATURE_CONSTRAINED_EXECUTION
using System.Runtime.ConstrainedExecution;
#endif
using System.Security;
using Microsoft.Build.Shared.FileSystem;
#if FEATURE_RESOURCE_EXPOSURE
using System.Runtime.Versioning;
#endif
 
#nullable disable
 
namespace Microsoft.Build.Shared
{
    /// <summary>
    /// Methods that are invoked on FileTracker.dll in order to handle inproc tracking
    /// </summary>
    /// <comments>
    /// We want to P/Invoke to the FileTracker methods, but FileTracker.dll is not guaranteed to be on PATH (since it's
    /// in the MSBuild directory), and there is no DefaultDllImportSearchPath that explicitly points to us. Thus, we
    /// are sneaking around P/Invoke by manually acquiring the method pointers and calling them ourselves. The vast
    /// majority of this code was lifted from ndp\fx\src\CLRCompression\ZLibNative.cs, which does the same thing for
    /// that assembly.
    /// </comments>
    internal static class InprocTrackingNativeMethods
    {
#pragma warning disable format // region formatting is different in net7.0 and net472, and cannot be fixed for both
        #region Delegates for the tracking functions
 
        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
#if FEATURE_SECURITY_PERMISSIONS
        [SuppressUnmanagedCodeSecurity]
#endif
        private delegate int StartTrackingContextDelegate([In, MarshalAs(UnmanagedType.LPWStr)] string intermediateDirectory, [In, MarshalAs(UnmanagedType.LPWStr)] string taskName);
 
        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
#if FEATURE_SECURITY_PERMISSIONS
        [SuppressUnmanagedCodeSecurity]
#endif
        private delegate int StartTrackingContextWithRootDelegate([In, MarshalAs(UnmanagedType.LPWStr)] string intermediateDirectory, [In, MarshalAs(UnmanagedType.LPWStr)] string taskName, [In, MarshalAs(UnmanagedType.LPWStr)] string rootMarker);
 
        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
#if FEATURE_SECURITY_PERMISSIONS
        [SuppressUnmanagedCodeSecurity]
#endif
        private delegate int EndTrackingContextDelegate();
 
        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
#if FEATURE_SECURITY_PERMISSIONS
        [SuppressUnmanagedCodeSecurity]
#endif
        private delegate int StopTrackingAndCleanupDelegate();
 
        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
#if FEATURE_SECURITY_PERMISSIONS
        [SuppressUnmanagedCodeSecurity]
#endif
        private delegate int SuspendTrackingDelegate();
 
        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
#if FEATURE_SECURITY_PERMISSIONS
        [SuppressUnmanagedCodeSecurity]
#endif
        private delegate int ResumeTrackingDelegate();
 
        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
#if FEATURE_SECURITY_PERMISSIONS
        [SuppressUnmanagedCodeSecurity]
#endif
        private delegate int WriteAllTLogsDelegate([In, MarshalAs(UnmanagedType.LPWStr)] string intermediateDirectory, [In, MarshalAs(UnmanagedType.LPWStr)] string tlogRootName);
 
        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
#if FEATURE_SECURITY_PERMISSIONS
        [SuppressUnmanagedCodeSecurity]
#endif
        private delegate int WriteContextTLogsDelegate([In, MarshalAs(UnmanagedType.LPWStr)] string intermediateDirectory, [In, MarshalAs(UnmanagedType.LPWStr)] string tlogRootName);
 
        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
#if FEATURE_SECURITY_PERMISSIONS
        [SuppressUnmanagedCodeSecurity]
#endif
        private delegate int SetThreadCountDelegate(int threadCount);
 
        #endregion // Delegates for the tracking functions
 
        #region Public API
 
        internal static void StartTrackingContext([In, MarshalAs(UnmanagedType.LPWStr)] string intermediateDirectory, [In, MarshalAs(UnmanagedType.LPWStr)] string taskName)
        {
            int hresult = FileTrackerDllStub.startTrackingContextDelegate(intermediateDirectory, taskName);
            Marshal.ThrowExceptionForHR(hresult, new IntPtr(-1));
        }
 
        internal static void StartTrackingContextWithRoot([In, MarshalAs(UnmanagedType.LPWStr)] string intermediateDirectory, [In, MarshalAs(UnmanagedType.LPWStr)] string taskName, [In, MarshalAs(UnmanagedType.LPWStr)] string rootMarker)
        {
            int hresult = FileTrackerDllStub.startTrackingContextWithRootDelegate(intermediateDirectory, taskName, rootMarker);
            Marshal.ThrowExceptionForHR(hresult, new IntPtr(-1));
        }
 
        internal static void EndTrackingContext()
        {
            int hresult = FileTrackerDllStub.endTrackingContextDelegate();
            Marshal.ThrowExceptionForHR(hresult, new IntPtr(-1));
        }
 
        internal static void StopTrackingAndCleanup()
        {
            int hresult = FileTrackerDllStub.stopTrackingAndCleanupDelegate();
            Marshal.ThrowExceptionForHR(hresult, new IntPtr(-1));
        }
 
        internal static void SuspendTracking()
        {
            int hresult = FileTrackerDllStub.suspendTrackingDelegate();
            Marshal.ThrowExceptionForHR(hresult, new IntPtr(-1));
        }
 
        internal static void ResumeTracking()
        {
            int hresult = FileTrackerDllStub.resumeTrackingDelegate();
            Marshal.ThrowExceptionForHR(hresult, new IntPtr(-1));
        }
 
        internal static void WriteAllTLogs([In, MarshalAs(UnmanagedType.LPWStr)] string intermediateDirectory, [In, MarshalAs(UnmanagedType.LPWStr)] string tlogRootName)
        {
            int hresult = FileTrackerDllStub.writeAllTLogsDelegate(intermediateDirectory, tlogRootName);
            Marshal.ThrowExceptionForHR(hresult, new IntPtr(-1));
        }
 
        internal static void WriteContextTLogs([In, MarshalAs(UnmanagedType.LPWStr)] string intermediateDirectory, [In, MarshalAs(UnmanagedType.LPWStr)] string tlogRootName)
        {
            int hresult = FileTrackerDllStub.writeContextTLogsDelegate(intermediateDirectory, tlogRootName);
            Marshal.ThrowExceptionForHR(hresult, new IntPtr(-1));
        }
 
        internal static void SetThreadCount(int threadCount)
        {
            int hresult = FileTrackerDllStub.setThreadCountDelegate(threadCount);
            Marshal.ThrowExceptionForHR(hresult, new IntPtr(-1));
        }
 
        #endregion // Public API
#pragma warning restore format
        private static class FileTrackerDllStub
        {
            private static readonly Lazy<string> fileTrackerDllName = new Lazy<string>(() => RuntimeInformation.ProcessArchitecture == Architecture.Arm64 ? "FileTrackerA4.dll" : (IntPtr.Size == sizeof(Int32)) ? "FileTracker32.dll" : "FileTracker64.dll");
 
            // Handle for FileTracker.dll itself
            [SecurityCritical]
            private static SafeHandle s_fileTrackerDllHandle;
#pragma warning disable format // region formatting is different in net7.0 and net472, and cannot be fixed for both
            #region Function pointers to native functions
 
            internal static StartTrackingContextDelegate startTrackingContextDelegate;
 
            internal static StartTrackingContextWithRootDelegate startTrackingContextWithRootDelegate;
 
            internal static EndTrackingContextDelegate endTrackingContextDelegate;
 
            internal static StopTrackingAndCleanupDelegate stopTrackingAndCleanupDelegate;
 
            internal static SuspendTrackingDelegate suspendTrackingDelegate;
 
            internal static ResumeTrackingDelegate resumeTrackingDelegate;
 
            internal static WriteAllTLogsDelegate writeAllTLogsDelegate;
 
            internal static WriteContextTLogsDelegate writeContextTLogsDelegate;
 
            internal static SetThreadCountDelegate setThreadCountDelegate;
 
            #endregion  // Function pointers to native functions
 
            #region Declarations of Windows API needed to load the native library
 
            [DllImport("kernel32.dll", CharSet = CharSet.Ansi, BestFitMapping = false)]
#if FEATURE_RESOURCE_EXPOSURE
            [ResourceExposure(ResourceScope.Process)]
#endif
            [SecurityCritical]
            private static extern IntPtr GetProcAddress(SafeHandle moduleHandle, String procName);
 
            [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
#if FEATURE_RESOURCE_EXPOSURE
            [ResourceExposure(ResourceScope.Machine)]
#endif
            [SecurityCritical]
            private static extern SafeLibraryHandle LoadLibrary(String libPath);
 
            #endregion // Declarations of Windows API needed to load the native library
 
            #region Initialization code
 
            /// <summary>
            /// Loads FileTracker.dll into a handle that we can use subsequently to grab the exported methods we're interested in.
            /// </summary>
            private static void LoadFileTrackerDll()
            {
                // Get the FileTracker in our directory that matches the currently running process
                string buildToolsPath = FrameworkLocationHelper.GeneratePathToBuildToolsForToolsVersion(MSBuildConstants.CurrentToolsVersion, DotNetFrameworkArchitecture.Current);
                string fileTrackerPath = Path.Combine(buildToolsPath, fileTrackerDllName.Value);
 
                if (!FileSystems.Default.FileExists(fileTrackerPath))
                {
                    throw new DllNotFoundException(fileTrackerDllName.Value);
                }
 
                SafeLibraryHandle handle = LoadLibrary(fileTrackerPath);
 
                if (handle.IsInvalid)
                {
                    Int32 hresult = Marshal.GetHRForLastWin32Error();
                    Marshal.ThrowExceptionForHR(hresult, new IntPtr(-1));
 
                    // If Marshal.ThrowExceptionForHR did not throw, we still need to make sure to throw:
                    throw new InvalidOperationException();
                }
 
                s_fileTrackerDllHandle = handle;
            }
 
            /// <summary>
            /// Generic code to grab the function pointer for a function exported by FileTracker.dll, given
            /// that function's name, and transform that function pointer into a callable delegate.
            /// </summary>
            [SecurityCritical]
            private static DT CreateDelegate<DT>(String entryPointName)
            {
                IntPtr entryPoint = GetProcAddress(s_fileTrackerDllHandle, entryPointName);
 
                if (IntPtr.Zero == entryPoint)
                {
                    throw new EntryPointNotFoundException(fileTrackerDllName.Value + "!" + entryPointName);
                }
 
                return (DT)(Object)Marshal.GetDelegateForFunctionPointer(entryPoint, typeof(DT));
            }
 
            /// <summary>
            /// Actually generate all of the delegates that will be called by our public (or rather, internal) surface area methods.
            /// </summary>
            private static void InitDelegates()
            {
                ErrorUtilities.VerifyThrow(s_fileTrackerDllHandle != null, "fileTrackerDllHandle should not be null");
                ErrorUtilities.VerifyThrow(!s_fileTrackerDllHandle.IsInvalid, "Handle for FileTracker.dll should not be invalid");
 
                startTrackingContextDelegate = CreateDelegate<StartTrackingContextDelegate>("StartTrackingContext");
                startTrackingContextWithRootDelegate = CreateDelegate<StartTrackingContextWithRootDelegate>("StartTrackingContextWithRoot");
                endTrackingContextDelegate = CreateDelegate<EndTrackingContextDelegate>("EndTrackingContext");
                stopTrackingAndCleanupDelegate = CreateDelegate<StopTrackingAndCleanupDelegate>("StopTrackingAndCleanup");
                suspendTrackingDelegate = CreateDelegate<SuspendTrackingDelegate>("SuspendTracking");
                resumeTrackingDelegate = CreateDelegate<ResumeTrackingDelegate>("ResumeTracking");
                writeAllTLogsDelegate = CreateDelegate<WriteAllTLogsDelegate>("WriteAllTLogs");
                writeContextTLogsDelegate = CreateDelegate<WriteContextTLogsDelegate>("WriteContextTLogs");
                setThreadCountDelegate = CreateDelegate<SetThreadCountDelegate>("SetThreadCount");
            }
 
            /// <summary>
            /// Static constructor -- generates the delegates for all of the export methods from
            /// FileTracker.dll that we care about.
            /// </summary>
            [SecuritySafeCritical]
            static FileTrackerDllStub()
            {
                LoadFileTrackerDll();
                InitDelegates();
            }
 
            #endregion  // Initialization code
#pragma warning restore format
            // Specialized handle to make sure we free FileTracker.dll
            [SecurityCritical]
            private class SafeLibraryHandle : SafeHandle
            {
                internal SafeLibraryHandle()
                    : base(IntPtr.Zero, true)
                {
                }
 
                public override bool IsInvalid
                {
                    [SecurityCritical]
                    get
                    { return IntPtr.Zero == handle; }
                }
#if FEATURE_CONSTRAINED_EXECUTION
                [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
#endif
                [SecurityCritical]
                protected override bool ReleaseHandle()
                {
                    // FileTracker expects to continue to exist even through ExitProcess -- if we forcibly unload it now,
                    // bad things can happen when the CLR attempts to call the (still detoured?) ExitProcess.
                    return true;
                }
            }  // private class SafeLibraryHandle
        }
    }
}
#endif