You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Add a layer of caching to avoid running msbuild as much as possible. #jira UE-109181 #rb ben.marsh #ROBOMERGE-SOURCE: CL 17102399 in //UE5/Main/... #ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v853-17066230) [CL 17102408 by jonathan adamczewski in ue5-release-engine-test branch]
1736 lines
70 KiB
C#
1736 lines
70 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Reflection;
|
|
using Microsoft.Win32;
|
|
using Microsoft.Win32.SafeHandles;
|
|
|
|
using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
|
|
using Microsoft.Build.Utilities;
|
|
|
|
namespace Microsoft.Build.Shared
|
|
{
|
|
/// <summary>
|
|
/// Interop methods.
|
|
/// </summary>
|
|
internal static class NativeMethodsShared
|
|
{
|
|
#region Constants
|
|
|
|
internal const uint ERROR_INSUFFICIENT_BUFFER = 0x8007007A;
|
|
internal const uint STARTUP_LOADER_SAFEMODE = 0x10;
|
|
internal const uint S_OK = 0x0;
|
|
internal const uint S_FALSE = 0x1;
|
|
internal const uint ERROR_ACCESS_DENIED = 0x5;
|
|
internal const uint ERROR_FILE_NOT_FOUND = 0x80070002;
|
|
internal const uint FUSION_E_PRIVATE_ASM_DISALLOWED = 0x80131044; // Tried to find unsigned assembly in GAC
|
|
internal const uint RUNTIME_INFO_DONT_SHOW_ERROR_DIALOG = 0x40;
|
|
internal const uint FILE_TYPE_CHAR = 0x0002;
|
|
internal const Int32 STD_OUTPUT_HANDLE = -11;
|
|
internal const uint RPC_S_CALLPENDING = 0x80010115;
|
|
internal const uint E_ABORT = (uint)0x80004004;
|
|
|
|
internal const int FILE_ATTRIBUTE_READONLY = 0x00000001;
|
|
internal const int FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
|
|
internal const int FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400;
|
|
|
|
/// <summary>
|
|
/// Default buffer size to use when dealing with the Windows API.
|
|
/// </summary>
|
|
internal const int MAX_PATH = 260;
|
|
|
|
private const string kernel32Dll = "kernel32.dll";
|
|
private const string mscoreeDLL = "mscoree.dll";
|
|
|
|
private const string WINDOWS_FILE_SYSTEM_REGISTRY_KEY = @"SYSTEM\CurrentControlSet\Control\FileSystem";
|
|
private const string WINDOWS_LONG_PATHS_ENABLED_VALUE_NAME = "LongPathsEnabled";
|
|
|
|
internal static DateTime MinFileDate { get; } = DateTime.FromFileTimeUtc(0);
|
|
|
|
#if FEATURE_HANDLEREF
|
|
internal static HandleRef NullHandleRef = new HandleRef(null, IntPtr.Zero);
|
|
#endif
|
|
|
|
internal static IntPtr NullIntPtr = new IntPtr(0);
|
|
|
|
// As defined in winnt.h:
|
|
internal const ushort PROCESSOR_ARCHITECTURE_INTEL = 0;
|
|
internal const ushort PROCESSOR_ARCHITECTURE_ARM = 5;
|
|
internal const ushort PROCESSOR_ARCHITECTURE_IA64 = 6;
|
|
internal const ushort PROCESSOR_ARCHITECTURE_AMD64 = 9;
|
|
internal const ushort PROCESSOR_ARCHITECTURE_ARM64 = 12;
|
|
|
|
internal const uint INFINITE = 0xFFFFFFFF;
|
|
internal const uint WAIT_ABANDONED_0 = 0x00000080;
|
|
internal const uint WAIT_OBJECT_0 = 0x00000000;
|
|
internal const uint WAIT_TIMEOUT = 0x00000102;
|
|
|
|
#if FEATURE_CHARSET_AUTO
|
|
internal const CharSet AutoOrUnicode = CharSet.Auto;
|
|
#else
|
|
internal const CharSet AutoOrUnicode = CharSet.Unicode;
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
#region Enums
|
|
|
|
private enum PROCESSINFOCLASS : int
|
|
{
|
|
ProcessBasicInformation = 0,
|
|
ProcessQuotaLimits,
|
|
ProcessIoCounters,
|
|
ProcessVmCounters,
|
|
ProcessTimes,
|
|
ProcessBasePriority,
|
|
ProcessRaisePriority,
|
|
ProcessDebugPort,
|
|
ProcessExceptionPort,
|
|
ProcessAccessToken,
|
|
ProcessLdtInformation,
|
|
ProcessLdtSize,
|
|
ProcessDefaultHardErrorMode,
|
|
ProcessIoPortHandlers, // Note: this is kernel mode only
|
|
ProcessPooledUsageAndLimits,
|
|
ProcessWorkingSetWatch,
|
|
ProcessUserModeIOPL,
|
|
ProcessEnableAlignmentFaultFixup,
|
|
ProcessPriorityClass,
|
|
ProcessWx86Information,
|
|
ProcessHandleCount,
|
|
ProcessAffinityMask,
|
|
ProcessPriorityBoost,
|
|
MaxProcessInfoClass
|
|
};
|
|
|
|
private enum eDesiredAccess : int
|
|
{
|
|
DELETE = 0x00010000,
|
|
READ_CONTROL = 0x00020000,
|
|
WRITE_DAC = 0x00040000,
|
|
WRITE_OWNER = 0x00080000,
|
|
SYNCHRONIZE = 0x00100000,
|
|
STANDARD_RIGHTS_ALL = 0x001F0000,
|
|
|
|
PROCESS_TERMINATE = 0x0001,
|
|
PROCESS_CREATE_THREAD = 0x0002,
|
|
PROCESS_SET_SESSIONID = 0x0004,
|
|
PROCESS_VM_OPERATION = 0x0008,
|
|
PROCESS_VM_READ = 0x0010,
|
|
PROCESS_VM_WRITE = 0x0020,
|
|
PROCESS_DUP_HANDLE = 0x0040,
|
|
PROCESS_CREATE_PROCESS = 0x0080,
|
|
PROCESS_SET_QUOTA = 0x0100,
|
|
PROCESS_SET_INFORMATION = 0x0200,
|
|
PROCESS_QUERY_INFORMATION = 0x0400,
|
|
PROCESS_ALL_ACCESS = SYNCHRONIZE | 0xFFF
|
|
}
|
|
#pragma warning disable 0649, 0169
|
|
internal enum LOGICAL_PROCESSOR_RELATIONSHIP
|
|
{
|
|
RelationProcessorCore,
|
|
RelationNumaNode,
|
|
RelationCache,
|
|
RelationProcessorPackage,
|
|
RelationGroup,
|
|
RelationAll = 0xffff
|
|
}
|
|
internal struct SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX
|
|
{
|
|
public LOGICAL_PROCESSOR_RELATIONSHIP Relationship;
|
|
public uint Size;
|
|
public PROCESSOR_RELATIONSHIP Processor;
|
|
}
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal unsafe struct PROCESSOR_RELATIONSHIP
|
|
{
|
|
public byte Flags;
|
|
private byte EfficiencyClass;
|
|
private fixed byte Reserved[20];
|
|
public ushort GroupCount;
|
|
public IntPtr GroupInfo;
|
|
}
|
|
#pragma warning restore 0169, 0149
|
|
|
|
/// <summary>
|
|
/// Flags for CoWaitForMultipleHandles
|
|
/// </summary>
|
|
[Flags]
|
|
public enum COWAIT_FLAGS : int
|
|
{
|
|
/// <summary>
|
|
/// Exit when a handle is signaled.
|
|
/// </summary>
|
|
COWAIT_NONE = 0,
|
|
|
|
/// <summary>
|
|
/// Exit when all handles are signaled AND a message is received.
|
|
/// </summary>
|
|
COWAIT_WAITALL = 0x00000001,
|
|
|
|
/// <summary>
|
|
/// Exit when an RPC call is serviced.
|
|
/// </summary>
|
|
COWAIT_ALERTABLE = 0x00000002
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processor architecture values
|
|
/// </summary>
|
|
internal enum ProcessorArchitectures
|
|
{
|
|
// Intel 32 bit
|
|
X86,
|
|
|
|
// AMD64 64 bit
|
|
X64,
|
|
|
|
// Itanium 64
|
|
IA64,
|
|
|
|
// ARM
|
|
ARM,
|
|
|
|
// ARM64
|
|
ARM64,
|
|
|
|
// Who knows
|
|
Unknown
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Structs
|
|
|
|
/// <summary>
|
|
/// Structure that contain information about the system on which we are running
|
|
/// </summary>
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal struct SYSTEM_INFO
|
|
{
|
|
// This is a union of a DWORD and a struct containing 2 WORDs.
|
|
internal ushort wProcessorArchitecture;
|
|
internal ushort wReserved;
|
|
|
|
internal uint dwPageSize;
|
|
internal IntPtr lpMinimumApplicationAddress;
|
|
internal IntPtr lpMaximumApplicationAddress;
|
|
internal IntPtr dwActiveProcessorMask;
|
|
internal uint dwNumberOfProcessors;
|
|
internal uint dwProcessorType;
|
|
internal uint dwAllocationGranularity;
|
|
internal ushort wProcessorLevel;
|
|
internal ushort wProcessorRevision;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wrap the intptr returned by OpenProcess in a safe handle.
|
|
/// </summary>
|
|
internal class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid
|
|
{
|
|
// Create a SafeHandle, informing the base class
|
|
// that this SafeHandle instance "owns" the handle,
|
|
// and therefore SafeHandle should call
|
|
// our ReleaseHandle method when the SafeHandle
|
|
// is no longer in use
|
|
private SafeProcessHandle() : base(true)
|
|
{
|
|
}
|
|
protected override bool ReleaseHandle()
|
|
{
|
|
return CloseHandle(handle);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains information about the current state of both physical and virtual memory, including extended memory
|
|
/// </summary>
|
|
[StructLayout(LayoutKind.Sequential, CharSet = AutoOrUnicode)]
|
|
internal class MemoryStatus
|
|
{
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="T:MemoryStatus"/> class.
|
|
/// </summary>
|
|
public MemoryStatus()
|
|
{
|
|
#if (CLR2COMPATIBILITY)
|
|
_length = (uint)Marshal.SizeOf(typeof(NativeMethodsShared.MemoryStatus));
|
|
#else
|
|
_length = (uint)Marshal.SizeOf<NativeMethodsShared.MemoryStatus>();
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Size of the structure, in bytes. You must set this member before calling GlobalMemoryStatusEx.
|
|
/// </summary>
|
|
private uint _length;
|
|
|
|
/// <summary>
|
|
/// Number between 0 and 100 that specifies the approximate percentage of physical
|
|
/// memory that is in use (0 indicates no memory use and 100 indicates full memory use).
|
|
/// </summary>
|
|
public uint MemoryLoad;
|
|
|
|
/// <summary>
|
|
/// Total size of physical memory, in bytes.
|
|
/// </summary>
|
|
public ulong TotalPhysical;
|
|
|
|
/// <summary>
|
|
/// Size of physical memory available, in bytes.
|
|
/// </summary>
|
|
public ulong AvailablePhysical;
|
|
|
|
/// <summary>
|
|
/// Size of the committed memory limit, in bytes. This is physical memory plus the
|
|
/// size of the page file, minus a small overhead.
|
|
/// </summary>
|
|
public ulong TotalPageFile;
|
|
|
|
/// <summary>
|
|
/// Size of available memory to commit, in bytes. The limit is ullTotalPageFile.
|
|
/// </summary>
|
|
public ulong AvailablePageFile;
|
|
|
|
/// <summary>
|
|
/// Total size of the user mode portion of the virtual address space of the calling process, in bytes.
|
|
/// </summary>
|
|
public ulong TotalVirtual;
|
|
|
|
/// <summary>
|
|
/// Size of unreserved and uncommitted memory in the user mode portion of the virtual
|
|
/// address space of the calling process, in bytes.
|
|
/// </summary>
|
|
public ulong AvailableVirtual;
|
|
|
|
/// <summary>
|
|
/// Size of unreserved and uncommitted memory in the extended portion of the virtual
|
|
/// address space of the calling process, in bytes.
|
|
/// </summary>
|
|
public ulong AvailableExtendedVirtual;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct PROCESS_BASIC_INFORMATION
|
|
{
|
|
public uint ExitStatus;
|
|
public IntPtr PebBaseAddress;
|
|
public UIntPtr AffinityMask;
|
|
public int BasePriority;
|
|
public UIntPtr UniqueProcessId;
|
|
public UIntPtr InheritedFromUniqueProcessId;
|
|
|
|
public uint Size
|
|
{
|
|
get
|
|
{
|
|
unsafe
|
|
{
|
|
return (uint)sizeof(PROCESS_BASIC_INFORMATION);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/// <summary>
|
|
/// Contains information about a file or directory; used by GetFileAttributesEx.
|
|
/// </summary>
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct WIN32_FILE_ATTRIBUTE_DATA
|
|
{
|
|
internal int fileAttributes;
|
|
internal uint ftCreationTimeLow;
|
|
internal uint ftCreationTimeHigh;
|
|
internal uint ftLastAccessTimeLow;
|
|
internal uint ftLastAccessTimeHigh;
|
|
internal uint ftLastWriteTimeLow;
|
|
internal uint ftLastWriteTimeHigh;
|
|
internal uint fileSizeHigh;
|
|
internal uint fileSizeLow;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contains the security descriptor for an object and specifies whether
|
|
/// the handle retrieved by specifying this structure is inheritable.
|
|
/// </summary>
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal class SecurityAttributes
|
|
{
|
|
public SecurityAttributes()
|
|
{
|
|
#if (CLR2COMPATIBILITY)
|
|
_nLength = (uint)Marshal.SizeOf(typeof(NativeMethodsShared.SecurityAttributes));
|
|
#else
|
|
_nLength = (uint)Marshal.SizeOf<NativeMethodsShared.SecurityAttributes>();
|
|
#endif
|
|
}
|
|
|
|
private uint _nLength;
|
|
|
|
public IntPtr lpSecurityDescriptor;
|
|
|
|
public bool bInheritHandle;
|
|
}
|
|
|
|
private class SystemInformationData
|
|
{
|
|
/// <summary>
|
|
/// Architecture as far as the current process is concerned.
|
|
/// It's x86 in wow64 (native architecture is x64 in that case).
|
|
/// Otherwise it's the same as the native architecture.
|
|
/// </summary>
|
|
public readonly ProcessorArchitectures ProcessorArchitectureType;
|
|
|
|
/// <summary>
|
|
/// Actual architecture of the system.
|
|
/// </summary>
|
|
public readonly ProcessorArchitectures ProcessorArchitectureTypeNative;
|
|
|
|
/// <summary>
|
|
/// Convert SYSTEM_INFO architecture values to the internal enum
|
|
/// </summary>
|
|
/// <param name="arch"></param>
|
|
/// <returns></returns>
|
|
private static ProcessorArchitectures ConvertSystemArchitecture(ushort arch)
|
|
{
|
|
return arch switch
|
|
{
|
|
PROCESSOR_ARCHITECTURE_INTEL => ProcessorArchitectures.X86,
|
|
PROCESSOR_ARCHITECTURE_AMD64 => ProcessorArchitectures.X64,
|
|
PROCESSOR_ARCHITECTURE_ARM => ProcessorArchitectures.ARM,
|
|
PROCESSOR_ARCHITECTURE_IA64 => ProcessorArchitectures.IA64,
|
|
PROCESSOR_ARCHITECTURE_ARM64 => ProcessorArchitectures.ARM64,
|
|
_ => ProcessorArchitectures.Unknown,
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read system info values
|
|
/// </summary>
|
|
public SystemInformationData()
|
|
{
|
|
ProcessorArchitectureType = ProcessorArchitectures.Unknown;
|
|
ProcessorArchitectureTypeNative = ProcessorArchitectures.Unknown;
|
|
|
|
if (IsWindows)
|
|
{
|
|
var systemInfo = new SYSTEM_INFO();
|
|
|
|
GetSystemInfo(ref systemInfo);
|
|
ProcessorArchitectureType = ConvertSystemArchitecture(systemInfo.wProcessorArchitecture);
|
|
|
|
GetNativeSystemInfo(ref systemInfo);
|
|
ProcessorArchitectureTypeNative = ConvertSystemArchitecture(systemInfo.wProcessorArchitecture);
|
|
}
|
|
else
|
|
{
|
|
ProcessorArchitectures processorArchitecture = ProcessorArchitectures.Unknown;
|
|
#if !NET35
|
|
// Get the architecture from the runtime.
|
|
processorArchitecture = RuntimeInformation.OSArchitecture switch
|
|
{
|
|
Architecture.Arm => ProcessorArchitectures.ARM,
|
|
Architecture.Arm64 => ProcessorArchitectures.ARM64,
|
|
Architecture.X64 => ProcessorArchitectures.X64,
|
|
Architecture.X86 => ProcessorArchitectures.X86,
|
|
_ => ProcessorArchitectures.Unknown,
|
|
};
|
|
#endif
|
|
// Fall back to 'uname -m' to get the architecture.
|
|
if (processorArchitecture == ProcessorArchitectures.Unknown)
|
|
{
|
|
try
|
|
{
|
|
// On Unix run 'uname -m' to get the architecture. It's common for Linux and Mac
|
|
using (
|
|
var proc =
|
|
Process.Start(
|
|
new ProcessStartInfo("uname")
|
|
{
|
|
Arguments = "-m",
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
CreateNoWindow = true
|
|
}))
|
|
{
|
|
string arch = null;
|
|
if (proc != null)
|
|
{
|
|
arch = proc.StandardOutput.ReadLine();
|
|
proc.WaitForExit();
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(arch))
|
|
{
|
|
if (arch.StartsWith("x86_64", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
ProcessorArchitectureType = ProcessorArchitectures.X64;
|
|
}
|
|
else if (arch.StartsWith("ia64", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
ProcessorArchitectureType = ProcessorArchitectures.IA64;
|
|
}
|
|
else if (arch.StartsWith("arm", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
ProcessorArchitectureType = ProcessorArchitectures.ARM;
|
|
}
|
|
else if (arch.StartsWith("aarch64", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
ProcessorArchitectureType = ProcessorArchitectures.ARM64;
|
|
}
|
|
else if (arch.StartsWith("i", StringComparison.OrdinalIgnoreCase)
|
|
&& arch.EndsWith("86", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
ProcessorArchitectureType = ProcessorArchitectures.X86;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Best effort: fall back to Unknown
|
|
}
|
|
}
|
|
|
|
ProcessorArchitectureTypeNative = ProcessorArchitectureType = processorArchitecture;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static int GetLogicalCoreCount()
|
|
{
|
|
int numberOfCpus = Environment.ProcessorCount;
|
|
#if !MONO
|
|
// .NET Core on Windows returns a core count limited to the current NUMA node
|
|
// https://github.com/dotnet/runtime/issues/29686
|
|
// so always double-check it.
|
|
if (IsWindows
|
|
#if NETFRAMEWORK
|
|
// .NET Framework calls Windows APIs that have a core count limit (32/64 depending on process bitness).
|
|
// So if we get a high core count on full framework, double-check it.
|
|
&& (numberOfCpus >= 32)
|
|
#endif
|
|
)
|
|
{
|
|
var result = GetLogicalCoreCountOnWindows();
|
|
if (result != -1)
|
|
{
|
|
numberOfCpus = result;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return numberOfCpus;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the exact physical core count on Windows
|
|
/// Useful for getting the exact core count in 32 bits processes,
|
|
/// as Environment.ProcessorCount has a 32-core limit in that case.
|
|
/// https://github.com/dotnet/runtime/blob/221ad5b728f93489655df290c1ea52956ad8f51c/src/libraries/System.Runtime.Extensions/src/System/Environment.Windows.cs#L171-L210
|
|
/// </summary>
|
|
private unsafe static int GetLogicalCoreCountOnWindows()
|
|
{
|
|
uint len = 0;
|
|
const int ERROR_INSUFFICIENT_BUFFER = 122;
|
|
|
|
if (!GetLogicalProcessorInformationEx(LOGICAL_PROCESSOR_RELATIONSHIP.RelationProcessorCore, IntPtr.Zero, ref len) &&
|
|
Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER)
|
|
{
|
|
// Allocate that much space
|
|
var buffer = new byte[len];
|
|
fixed (byte* bufferPtr = buffer)
|
|
{
|
|
// Call GetLogicalProcessorInformationEx with the allocated buffer
|
|
if (GetLogicalProcessorInformationEx(LOGICAL_PROCESSOR_RELATIONSHIP.RelationProcessorCore, (IntPtr)bufferPtr, ref len))
|
|
{
|
|
// Walk each SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX in the buffer, where the Size of each dictates how
|
|
// much space it's consuming. For each group relation, count the number of active processors in each of its group infos.
|
|
int processorCount = 0;
|
|
byte* ptr = bufferPtr;
|
|
byte* endPtr = bufferPtr + len;
|
|
while (ptr < endPtr)
|
|
{
|
|
var current = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)ptr;
|
|
if (current->Relationship == LOGICAL_PROCESSOR_RELATIONSHIP.RelationProcessorCore)
|
|
{
|
|
// Flags is 0 if the core has a single logical proc, LTP_PC_SMT if more than one
|
|
// for now, assume "more than 1" == 2, as it has historically been for hyperthreading
|
|
processorCount += (current->Processor.Flags == 0) ? 1 : 2;
|
|
}
|
|
ptr += current->Size;
|
|
}
|
|
return processorCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Member data
|
|
|
|
internal static bool HasMaxPath => MaxPath == MAX_PATH;
|
|
|
|
/// <summary>
|
|
/// Gets the max path limit of the current OS.
|
|
/// </summary>
|
|
internal static int MaxPath
|
|
{
|
|
get
|
|
{
|
|
if (!IsMaxPathSet)
|
|
{
|
|
SetMaxPath();
|
|
}
|
|
return _maxPath;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cached value for MaxPath.
|
|
/// </summary>
|
|
private static int _maxPath;
|
|
|
|
private static bool IsMaxPathSet { get; set; }
|
|
|
|
private static readonly object MaxPathLock = new object();
|
|
|
|
private static void SetMaxPath()
|
|
{
|
|
lock (MaxPathLock)
|
|
{
|
|
if (!IsMaxPathSet)
|
|
{
|
|
bool isMaxPathRestricted = Traits.Instance.EscapeHatches.DisableLongPaths || IsMaxPathLegacyWindows();
|
|
_maxPath = isMaxPathRestricted ? MAX_PATH : int.MaxValue;
|
|
IsMaxPathSet = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static bool IsMaxPathLegacyWindows()
|
|
{
|
|
try
|
|
{
|
|
return IsWindows && !IsLongPathsEnabledRegistry();
|
|
}
|
|
catch
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private static bool IsLongPathsEnabledRegistry()
|
|
{
|
|
using (RegistryKey fileSystemKey = Registry.LocalMachine.OpenSubKey(WINDOWS_FILE_SYSTEM_REGISTRY_KEY))
|
|
{
|
|
object longPathsEnabledValue = fileSystemKey?.GetValue(WINDOWS_LONG_PATHS_ENABLED_VALUE_NAME, 0);
|
|
return fileSystemKey != null && Convert.ToInt32(longPathsEnabledValue) == 1;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cached value for IsUnixLike (this method is called frequently during evaluation).
|
|
/// </summary>
|
|
private static readonly bool s_isUnixLike = IsLinux || IsOSX || IsBSD;
|
|
|
|
/// <summary>
|
|
/// Gets a flag indicating if we are running under a Unix-like system (Mac, Linux, etc.)
|
|
/// </summary>
|
|
internal static bool IsUnixLike
|
|
{
|
|
get { return s_isUnixLike; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a flag indicating if we are running under Linux
|
|
/// </summary>
|
|
internal static bool IsLinux
|
|
{
|
|
#if CLR2COMPATIBILITY
|
|
get { return false; }
|
|
#else
|
|
get { return RuntimeInformation.IsOSPlatform(OSPlatform.Linux); }
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a flag indicating if we are running under flavor of BSD (NetBSD, OpenBSD, FreeBSD)
|
|
/// </summary>
|
|
internal static bool IsBSD
|
|
{
|
|
#if CLR2COMPATIBILITY
|
|
get { return false; }
|
|
#else
|
|
get
|
|
{
|
|
return RuntimeInformation.IsOSPlatform(OSPlatform.Create("FREEBSD")) ||
|
|
RuntimeInformation.IsOSPlatform(OSPlatform.Create("NETBSD")) ||
|
|
RuntimeInformation.IsOSPlatform(OSPlatform.Create("OPENBSD"));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if EPIC_GAMES_REMOVED
|
|
private static readonly object IsMonoLock = new object();
|
|
|
|
private static bool? _isMono;
|
|
|
|
/// <summary>
|
|
/// Gets a flag indicating if we are running under MONO
|
|
/// </summary>
|
|
internal static bool IsMono
|
|
{
|
|
get
|
|
{
|
|
if (_isMono != null) return _isMono.Value;
|
|
|
|
lock (IsMonoLock)
|
|
{
|
|
if (_isMono == null)
|
|
{
|
|
// There could be potentially expensive TypeResolve events, so cache IsMono.
|
|
// Also, VS does not host Mono runtimes, so turn IsMono off when msbuild is running under VS
|
|
_isMono = !BuildEnvironmentHelper.Instance.RunningInVisualStudio &&
|
|
Type.GetType("Mono.Runtime") != null;
|
|
}
|
|
}
|
|
|
|
return _isMono.Value;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if !CLR2COMPATIBILITY
|
|
private static bool? _isWindows;
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Gets a flag indicating if we are running under some version of Windows
|
|
/// </summary>
|
|
internal static bool IsWindows
|
|
{
|
|
#if CLR2COMPATIBILITY
|
|
get { return true; }
|
|
#else
|
|
get {
|
|
if (_isWindows == null)
|
|
{
|
|
_isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
|
}
|
|
return _isWindows.Value;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if !CLR2COMPATIBILITY
|
|
private static bool? _isOSX;
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Gets a flag indicating if we are running under Mac OSX
|
|
/// </summary>
|
|
internal static bool IsOSX
|
|
{
|
|
#if CLR2COMPATIBILITY
|
|
get { return false; }
|
|
#else
|
|
get {
|
|
if (_isOSX == null)
|
|
{
|
|
_isOSX = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
|
}
|
|
return _isOSX.Value;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a string for the current OS. This matches the OS env variable
|
|
/// for Windows (Windows_NT).
|
|
/// </summary>
|
|
internal static string OSName
|
|
{
|
|
get { return IsWindows ? "Windows_NT" : "Unix"; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// OS name that can be used for the msbuildExtensionsPathSearchPaths element
|
|
/// for a toolset
|
|
/// </summary>
|
|
internal static string GetOSNameForExtensionsPath()
|
|
{
|
|
return IsOSX ? "osx" : IsUnixLike ? "unix" : "windows";
|
|
}
|
|
|
|
internal static bool OSUsesCaseSensitivePaths
|
|
{
|
|
get { return IsLinux; }
|
|
}
|
|
|
|
#if EPIC_GAMES_REMOVED
|
|
/// <summary>
|
|
/// The base directory for all framework paths in Mono
|
|
/// </summary>
|
|
private static string s_frameworkBasePath;
|
|
|
|
/// <summary>
|
|
/// The directory of the current framework
|
|
/// </summary>
|
|
private static string s_frameworkCurrentPath;
|
|
|
|
/// <summary>
|
|
/// Gets the currently running framework path
|
|
/// </summary>
|
|
internal static string FrameworkCurrentPath
|
|
{
|
|
get
|
|
{
|
|
if (s_frameworkCurrentPath == null)
|
|
{
|
|
var baseTypeLocation = AssemblyUtilities.GetAssemblyLocation(typeof(string).GetTypeInfo().Assembly);
|
|
|
|
s_frameworkCurrentPath =
|
|
Path.GetDirectoryName(baseTypeLocation)
|
|
?? string.Empty;
|
|
}
|
|
|
|
return s_frameworkCurrentPath;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the base directory of all Mono frameworks
|
|
/// </summary>
|
|
internal static string FrameworkBasePath
|
|
{
|
|
get
|
|
{
|
|
if (s_frameworkBasePath == null)
|
|
{
|
|
var dir = FrameworkCurrentPath;
|
|
if (dir != string.Empty)
|
|
{
|
|
dir = Path.GetDirectoryName(dir);
|
|
}
|
|
|
|
s_frameworkBasePath = dir ?? string.Empty;
|
|
}
|
|
|
|
return s_frameworkBasePath;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// System information, initialized when required.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Initially implemented as <see cref="Lazy{SystemInformationData}"/>, but
|
|
/// that's .NET 4+, and this is used in MSBuildTaskHost.
|
|
/// </remarks>
|
|
private static SystemInformationData SystemInformation
|
|
{
|
|
get
|
|
{
|
|
if (!_systemInformationInitialized)
|
|
{
|
|
lock (SystemInformationLock)
|
|
{
|
|
if (!_systemInformationInitialized)
|
|
{
|
|
_systemInformation = new SystemInformationData();
|
|
_systemInformationInitialized = true;
|
|
}
|
|
}
|
|
}
|
|
return _systemInformation;
|
|
}
|
|
}
|
|
|
|
private static SystemInformationData _systemInformation;
|
|
private static bool _systemInformationInitialized;
|
|
private static readonly object SystemInformationLock = new object();
|
|
|
|
/// <summary>
|
|
/// Architecture getter
|
|
/// </summary>
|
|
internal static ProcessorArchitectures ProcessorArchitecture => SystemInformation.ProcessorArchitectureType;
|
|
|
|
/// <summary>
|
|
/// Native architecture getter
|
|
/// </summary>
|
|
internal static ProcessorArchitectures ProcessorArchitectureNative => SystemInformation.ProcessorArchitectureTypeNative;
|
|
|
|
#endregion
|
|
|
|
#region Set Error Mode (copied from BCL)
|
|
|
|
private static readonly Version s_threadErrorModeMinOsVersion = new Version(6, 1, 0x1db0);
|
|
|
|
internal static int SetErrorMode(int newMode)
|
|
{
|
|
#if FEATURE_OSVERSION
|
|
if (Environment.OSVersion.Version < s_threadErrorModeMinOsVersion)
|
|
{
|
|
return SetErrorMode_VistaAndOlder(newMode);
|
|
}
|
|
#endif
|
|
int num;
|
|
SetErrorMode_Win7AndNewer(newMode, out num);
|
|
return num;
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport("kernel32.dll", EntryPoint = "SetThreadErrorMode", SetLastError = true)]
|
|
private static extern bool SetErrorMode_Win7AndNewer(int newMode, out int oldMode);
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport("kernel32.dll", EntryPoint = "SetErrorMode", ExactSpelling = true)]
|
|
private static extern int SetErrorMode_VistaAndOlder(int newMode);
|
|
|
|
#endregion
|
|
|
|
#region Wrapper methods
|
|
|
|
/// <summary>
|
|
/// Really truly non pumping wait.
|
|
/// Raw IntPtrs have to be used, because the marshaller does not support arrays of SafeHandle, only
|
|
/// single SafeHandles.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
|
|
public static extern Int32 WaitForMultipleObjects(uint handle, IntPtr[] handles, bool waitAll, uint milliseconds);
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
internal static extern void GetSystemInfo(ref SYSTEM_INFO lpSystemInfo);
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
internal static extern void GetNativeSystemInfo(ref SYSTEM_INFO lpSystemInfo);
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
internal static extern bool GetLogicalProcessorInformationEx(LOGICAL_PROCESSOR_RELATIONSHIP RelationshipType, IntPtr Buffer, ref uint ReturnedLength);
|
|
|
|
/// <summary>
|
|
/// Get the last write time of the fullpath to a directory. If the pointed path is not a directory, or
|
|
/// if the directory does not exist, then false is returned and fileModifiedTimeUtc is set DateTime.MinValue.
|
|
/// </summary>
|
|
/// <param name="fullPath">Full path to the file in the filesystem</param>
|
|
/// <param name="fileModifiedTimeUtc">The UTC last write time for the directory</param>
|
|
internal static bool GetLastWriteDirectoryUtcTime(string fullPath, out DateTime fileModifiedTimeUtc)
|
|
{
|
|
// This code was copied from the reference manager, if there is a bug fix in that code, see if the same fix should also be made
|
|
// there
|
|
if (IsWindows)
|
|
{
|
|
fileModifiedTimeUtc = DateTime.MinValue;
|
|
|
|
WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA();
|
|
bool success = GetFileAttributesEx(fullPath, 0, ref data);
|
|
if (success)
|
|
{
|
|
if ((data.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
|
|
{
|
|
long dt = ((long)(data.ftLastWriteTimeHigh) << 32) | ((long)data.ftLastWriteTimeLow);
|
|
fileModifiedTimeUtc = DateTime.FromFileTimeUtc(dt);
|
|
}
|
|
else
|
|
{
|
|
// Path does not point to a directory
|
|
success = false;
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
if (Directory.Exists(fullPath))
|
|
{
|
|
fileModifiedTimeUtc = Directory.GetLastWriteTimeUtc(fullPath);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
fileModifiedTimeUtc = DateTime.MinValue;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Takes the path and returns the short path
|
|
/// </summary>
|
|
internal static string GetShortFilePath(string path)
|
|
{
|
|
if (!IsWindows)
|
|
{
|
|
return path;
|
|
}
|
|
|
|
if (path != null)
|
|
{
|
|
int length = GetShortPathName(path, null, 0);
|
|
int errorCode = Marshal.GetLastWin32Error();
|
|
|
|
if (length > 0)
|
|
{
|
|
StringBuilder fullPathBuffer = new StringBuilder(length);
|
|
length = GetShortPathName(path, fullPathBuffer, length);
|
|
errorCode = Marshal.GetLastWin32Error();
|
|
|
|
if (length > 0)
|
|
{
|
|
string fullPath = fullPathBuffer.ToString();
|
|
path = fullPath;
|
|
}
|
|
}
|
|
|
|
if (length == 0 && errorCode != 0)
|
|
{
|
|
ThrowExceptionForErrorCode(errorCode);
|
|
}
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Takes the path and returns a full path
|
|
/// </summary>
|
|
/// <param name="path"></param>
|
|
/// <returns></returns>
|
|
internal static string GetLongFilePath(string path)
|
|
{
|
|
if (IsUnixLike)
|
|
{
|
|
return path;
|
|
}
|
|
|
|
if (path != null)
|
|
{
|
|
int length = GetLongPathName(path, null, 0);
|
|
int errorCode = Marshal.GetLastWin32Error();
|
|
|
|
if (length > 0)
|
|
{
|
|
StringBuilder fullPathBuffer = new StringBuilder(length);
|
|
length = GetLongPathName(path, fullPathBuffer, length);
|
|
errorCode = Marshal.GetLastWin32Error();
|
|
|
|
if (length > 0)
|
|
{
|
|
string fullPath = fullPathBuffer.ToString();
|
|
path = fullPath;
|
|
}
|
|
}
|
|
|
|
if (length == 0 && errorCode != 0)
|
|
{
|
|
ThrowExceptionForErrorCode(errorCode);
|
|
}
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves the current global memory status.
|
|
/// </summary>
|
|
internal static MemoryStatus GetMemoryStatus()
|
|
{
|
|
if (NativeMethodsShared.IsWindows)
|
|
{
|
|
MemoryStatus status = new MemoryStatus();
|
|
bool returnValue = NativeMethodsShared.GlobalMemoryStatusEx(status);
|
|
if (!returnValue)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the last write time of the fullpath to the file.
|
|
/// </summary>
|
|
/// <param name="fullPath">Full path to the file in the filesystem</param>
|
|
/// <returns>The last write time of the file, or DateTime.MinValue if the file does not exist.</returns>
|
|
/// <remarks>
|
|
/// This method should be accurate for regular files and symlinks, but can report incorrect data
|
|
/// if the file's content was modified by writing to it through a different link, unless
|
|
/// MSBUILDALWAYSCHECKCONTENTTIMESTAMP=1.
|
|
/// </remarks>
|
|
internal static DateTime GetLastWriteFileUtcTime(string fullPath)
|
|
{
|
|
DateTime fileModifiedTime = DateTime.MinValue;
|
|
|
|
if (IsWindows)
|
|
{
|
|
if (Traits.Instance.EscapeHatches.AlwaysUseContentTimestamp)
|
|
{
|
|
return GetContentLastWriteFileUtcTime(fullPath);
|
|
}
|
|
|
|
WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA();
|
|
bool success = NativeMethodsShared.GetFileAttributesEx(fullPath, 0, ref data);
|
|
|
|
if (success && (data.fileAttributes & NativeMethodsShared.FILE_ATTRIBUTE_DIRECTORY) == 0)
|
|
{
|
|
long dt = ((long)(data.ftLastWriteTimeHigh) << 32) | ((long)data.ftLastWriteTimeLow);
|
|
fileModifiedTime = DateTime.FromFileTimeUtc(dt);
|
|
|
|
// If file is a symlink _and_ we're not instructed to do the wrong thing, get a more accurate timestamp.
|
|
if ((data.fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT && !Traits.Instance.EscapeHatches.UseSymlinkTimeInsteadOfTargetTime)
|
|
{
|
|
fileModifiedTime = GetContentLastWriteFileUtcTime(fullPath);
|
|
}
|
|
}
|
|
|
|
return fileModifiedTime;
|
|
}
|
|
else
|
|
{
|
|
return File.Exists(fullPath)
|
|
? File.GetLastWriteTimeUtc(fullPath)
|
|
: DateTime.MinValue;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the last write time of the content pointed to by a file path.
|
|
/// </summary>
|
|
/// <param name="fullPath">Full path to the file in the filesystem</param>
|
|
/// <returns>The last write time of the file, or DateTime.MinValue if the file does not exist.</returns>
|
|
/// <remarks>
|
|
/// This is the most accurate timestamp-extraction mechanism, but it is too slow to use all the time.
|
|
/// See https://github.com/Microsoft/msbuild/issues/2052.
|
|
/// </remarks>
|
|
private static DateTime GetContentLastWriteFileUtcTime(string fullPath)
|
|
{
|
|
DateTime fileModifiedTime = DateTime.MinValue;
|
|
|
|
using (SafeFileHandle handle =
|
|
CreateFile(fullPath,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
IntPtr.Zero,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL, /* No FILE_FLAG_OPEN_REPARSE_POINT; read through to content */
|
|
IntPtr.Zero))
|
|
{
|
|
if (!handle.IsInvalid)
|
|
{
|
|
FILETIME ftCreationTime, ftLastAccessTime, ftLastWriteTime;
|
|
if (GetFileTime(handle, out ftCreationTime, out ftLastAccessTime, out ftLastWriteTime))
|
|
{
|
|
long fileTime = ((long)(uint)ftLastWriteTime.dwHighDateTime) << 32 |
|
|
(long)(uint)ftLastWriteTime.dwLowDateTime;
|
|
fileModifiedTime =
|
|
DateTime.FromFileTimeUtc(fileTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
return fileModifiedTime;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Did the HRESULT succeed
|
|
/// </summary>
|
|
public static bool HResultSucceeded(int hr)
|
|
{
|
|
return hr >= 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Did the HRESULT Fail
|
|
/// </summary>
|
|
public static bool HResultFailed(int hr)
|
|
{
|
|
return hr < 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Given an error code, converts it to an HRESULT and throws the appropriate exception.
|
|
/// </summary>
|
|
/// <param name="errorCode"></param>
|
|
public static void ThrowExceptionForErrorCode(int errorCode)
|
|
{
|
|
// See ndp\clr\src\bcl\system\io\__error.cs for this code as it appears in the CLR.
|
|
|
|
// Something really bad went wrong with the call
|
|
// translate the error into an exception
|
|
|
|
// Convert the errorcode into an HRESULT (See MakeHRFromErrorCode in Win32Native.cs in
|
|
// ndp\clr\src\bcl\microsoft\win32)
|
|
errorCode = unchecked(((int)0x80070000) | errorCode);
|
|
|
|
// Throw an exception as best we can
|
|
Marshal.ThrowExceptionForHR(errorCode);
|
|
}
|
|
|
|
#if EPIC_GAMES_REMOVED
|
|
/// <summary>
|
|
/// Kills the specified process by id and all of its children recursively.
|
|
/// </summary>
|
|
internal static void KillTree(int processIdToKill)
|
|
{
|
|
// Note that GetProcessById does *NOT* internally hold on to the process handle.
|
|
// Only when you create the process using the Process object
|
|
// does the Process object retain the original handle.
|
|
|
|
Process thisProcess;
|
|
try
|
|
{
|
|
thisProcess = Process.GetProcessById(processIdToKill);
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
// The process has already died for some reason. So shrug and assume that any child processes
|
|
// have all also either died or are in the process of doing so.
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
DateTime myStartTime = thisProcess.StartTime;
|
|
|
|
// Grab the process handle. We want to keep this open for the duration of the function so that
|
|
// it cannot be reused while we are running.
|
|
SafeProcessHandle hProcess = OpenProcess(eDesiredAccess.PROCESS_QUERY_INFORMATION, false, processIdToKill);
|
|
if (hProcess.IsInvalid)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
try
|
|
{
|
|
// Kill this process, so that no further children can be created.
|
|
thisProcess.Kill();
|
|
}
|
|
catch (Win32Exception e)
|
|
{
|
|
// Access denied is potentially expected -- it happens when the process that
|
|
// we're attempting to kill is already dead. So just ignore in that case.
|
|
if (e.NativeErrorCode != ERROR_ACCESS_DENIED)
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// Now enumerate our children. Children of this process are any process which has this process id as its parent
|
|
// and which also started after this process did.
|
|
List<KeyValuePair<int, SafeProcessHandle>> children = GetChildProcessIds(processIdToKill, myStartTime);
|
|
|
|
try
|
|
{
|
|
foreach (KeyValuePair<int, SafeProcessHandle> childProcessInfo in children)
|
|
{
|
|
KillTree(childProcessInfo.Key);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
foreach (KeyValuePair<int, SafeProcessHandle> childProcessInfo in children)
|
|
{
|
|
childProcessInfo.Value.Dispose();
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
// Release the handle. After this point no more children of this process exist and this process has also exited.
|
|
hProcess.Dispose();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
thisProcess.Dispose();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the parent process id for the specified process.
|
|
/// Returns zero if it cannot be gotten for some reason.
|
|
/// </summary>
|
|
internal static int GetParentProcessId(int processId)
|
|
{
|
|
int ParentID = 0;
|
|
#if !CLR2COMPATIBILITY
|
|
if (IsUnixLike)
|
|
{
|
|
string line = null;
|
|
|
|
try
|
|
{
|
|
// /proc/<processID>/stat returns a bunch of space separated fields. Get that string
|
|
using (var r = FileUtilities.OpenRead("/proc/" + processId + "/stat"))
|
|
{
|
|
line = r.ReadLine();
|
|
}
|
|
}
|
|
catch // Ignore errors since the process may have terminated
|
|
{
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(line))
|
|
{
|
|
// One of the fields is the process name. It may contain any characters, but since it's
|
|
// in parenthesis, we can finds its end by looking for the last parenthesis. After that,
|
|
// there comes a space, then the second fields separated by a space is the parent id.
|
|
string[] statFields = line.Substring(line.LastIndexOf(')')).Split(MSBuildConstants.SpaceChar, 4);
|
|
if (statFields.Length >= 3)
|
|
{
|
|
ParentID = Int32.Parse(statFields[2]);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
SafeProcessHandle hProcess = OpenProcess(eDesiredAccess.PROCESS_QUERY_INFORMATION, false, processId);
|
|
|
|
if (!hProcess.IsInvalid)
|
|
{
|
|
try
|
|
{
|
|
// UNDONE: NtQueryInformationProcess will fail if we are not elevated and other process is. Advice is to change to use ToolHelp32 API's
|
|
// For now just return zero and worst case we will not kill some children.
|
|
PROCESS_BASIC_INFORMATION pbi = new PROCESS_BASIC_INFORMATION();
|
|
int pSize = 0;
|
|
|
|
if (0 == NtQueryInformationProcess(hProcess, PROCESSINFOCLASS.ProcessBasicInformation, ref pbi, pbi.Size, ref pSize))
|
|
{
|
|
ParentID = (int)pbi.InheritedFromUniqueProcessId;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
hProcess.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
return ParentID;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an array of all the immediate child processes by id.
|
|
/// NOTE: The IntPtr in the tuple is the handle of the child process. CloseHandle MUST be called on this.
|
|
/// </summary>
|
|
internal static List<KeyValuePair<int, SafeProcessHandle>> GetChildProcessIds(int parentProcessId, DateTime parentStartTime)
|
|
{
|
|
List<KeyValuePair<int, SafeProcessHandle>> myChildren = new List<KeyValuePair<int, SafeProcessHandle>>();
|
|
|
|
foreach (Process possibleChildProcess in Process.GetProcesses())
|
|
{
|
|
using (possibleChildProcess)
|
|
{
|
|
// Hold the child process handle open so that children cannot die and restart with a different parent after we've started looking at it.
|
|
// This way, any handle we pass back is guaranteed to be one of our actual children.
|
|
SafeProcessHandle childHandle = OpenProcess(eDesiredAccess.PROCESS_QUERY_INFORMATION, false, possibleChildProcess.Id);
|
|
if (childHandle.IsInvalid)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool keepHandle = false;
|
|
try
|
|
{
|
|
if (possibleChildProcess.StartTime > parentStartTime)
|
|
{
|
|
int childParentProcessId = GetParentProcessId(possibleChildProcess.Id);
|
|
if (childParentProcessId != 0)
|
|
{
|
|
if (parentProcessId == childParentProcessId)
|
|
{
|
|
// Add this one
|
|
myChildren.Add(new KeyValuePair<int, SafeProcessHandle>(possibleChildProcess.Id, childHandle));
|
|
keepHandle = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (!keepHandle)
|
|
{
|
|
childHandle.Dispose();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return myChildren;
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Internal, optimized GetCurrentDirectory implementation that simply delegates to the native method
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
internal unsafe static string GetCurrentDirectory()
|
|
{
|
|
#if FEATURE_LEGACY_GETCURRENTDIRECTORY
|
|
if (IsWindows)
|
|
{
|
|
int bufferSize = GetCurrentDirectoryWin32(0, null);
|
|
char* buffer = stackalloc char[bufferSize];
|
|
int pathLength = GetCurrentDirectoryWin32(bufferSize, buffer);
|
|
return new string(buffer, startIndex: 0, length: pathLength);
|
|
}
|
|
#endif
|
|
return Directory.GetCurrentDirectory();
|
|
}
|
|
|
|
private unsafe static int GetCurrentDirectoryWin32(int nBufferLength, char* lpBuffer)
|
|
{
|
|
int pathLength = GetCurrentDirectory(nBufferLength, lpBuffer);
|
|
VerifyThrowWin32Result(pathLength);
|
|
return pathLength;
|
|
}
|
|
|
|
internal unsafe static string GetFullPath(string path)
|
|
{
|
|
int bufferSize = GetFullPathWin32(path, 0, null, IntPtr.Zero);
|
|
char* buffer = stackalloc char[bufferSize];
|
|
int fullPathLength = GetFullPathWin32(path, bufferSize, buffer, IntPtr.Zero);
|
|
// Avoid creating new strings unnecessarily
|
|
return AreStringsEqual(buffer, fullPathLength, path) ? path : new string(buffer, startIndex: 0, length: fullPathLength);
|
|
}
|
|
|
|
private unsafe static int GetFullPathWin32(string target, int bufferLength, char* buffer, IntPtr mustBeZero)
|
|
{
|
|
int pathLength = GetFullPathName(target, bufferLength, buffer, mustBeZero);
|
|
VerifyThrowWin32Result(pathLength);
|
|
return pathLength;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compare an unsafe char buffer with a <see cref="System.String"/> to see if their contents are identical.
|
|
/// </summary>
|
|
/// <param name="buffer">The beginning of the char buffer.</param>
|
|
/// <param name="len">The length of the buffer.</param>
|
|
/// <param name="s">The string.</param>
|
|
/// <returns>True only if the contents of <paramref name="s"/> and the first <paramref name="len"/> characters in <paramref name="buffer"/> are identical.</returns>
|
|
private unsafe static bool AreStringsEqual(char* buffer, int len, string s)
|
|
{
|
|
if (len != s.Length)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (char ch in s)
|
|
{
|
|
if (ch != *buffer++)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
internal static void VerifyThrowWin32Result(int result)
|
|
{
|
|
bool isError = result == 0;
|
|
if (isError)
|
|
{
|
|
int code = Marshal.GetLastWin32Error();
|
|
ThrowExceptionForErrorCode(code);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region PInvoke
|
|
|
|
/// <summary>
|
|
/// Gets the current OEM code page which is used by console apps
|
|
/// (as opposed to the Windows/ANSI code page)
|
|
/// Basically for each ANSI code page (set in Regional settings) there's a corresponding OEM code page
|
|
/// that needs to be used for instance when writing to batch files
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport(kernel32Dll)]
|
|
internal static extern int GetOEMCP();
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
internal static extern bool GetFileAttributesEx(String name, int fileInfoLevel, ref WIN32_FILE_ATTRIBUTE_DATA lpFileInformation);
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport(kernel32Dll, SetLastError = true, CharSet = CharSet.Unicode)]
|
|
private static extern uint SearchPath
|
|
(
|
|
string path,
|
|
string fileName,
|
|
string extension,
|
|
int numBufferChars,
|
|
[Out] StringBuilder buffer,
|
|
int[] filePart
|
|
);
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport("kernel32.dll", PreserveSig = true, SetLastError = true)]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
internal static extern bool FreeLibrary([In] IntPtr module);
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport("kernel32.dll", PreserveSig = true, BestFitMapping = false, ThrowOnUnmappableChar = true, CharSet = CharSet.Ansi, SetLastError = true)]
|
|
internal static extern IntPtr GetProcAddress(IntPtr module, string procName);
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, PreserveSig = true, SetLastError = true)]
|
|
internal static extern IntPtr LoadLibrary(string fileName);
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport(mscoreeDLL, SetLastError = true, CharSet = CharSet.Unicode)]
|
|
internal static extern uint GetRequestedRuntimeInfo(String pExe,
|
|
String pwszVersion,
|
|
String pConfigurationFile,
|
|
uint startupFlags,
|
|
uint runtimeInfoFlags,
|
|
[Out] StringBuilder pDirectory,
|
|
int dwDirectory,
|
|
out uint dwDirectoryLength,
|
|
[Out] StringBuilder pVersion,
|
|
int cchBuffer,
|
|
out uint dwlength);
|
|
|
|
/// <summary>
|
|
/// Gets the fully qualified filename of the currently executing .exe
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport(kernel32Dll, SetLastError = true, CharSet = CharSet.Unicode)]
|
|
internal static extern int GetModuleFileName(
|
|
#if FEATURE_HANDLEREF
|
|
HandleRef hModule,
|
|
#else
|
|
IntPtr hModule,
|
|
#endif
|
|
[Out] StringBuilder buffer, int length);
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport("kernel32.dll")]
|
|
internal static extern IntPtr GetStdHandle(int nStdHandle);
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport("kernel32.dll")]
|
|
internal static extern uint GetFileType(IntPtr hFile);
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[SuppressMessage("Microsoft.Usage", "CA2205:UseManagedEquivalentsOfWin32Api", Justification = "Using unmanaged equivalent for performance reasons")]
|
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
internal unsafe static extern int GetCurrentDirectory(int nBufferLength, char* lpBuffer);
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[SuppressMessage("Microsoft.Usage", "CA2205:UseManagedEquivalentsOfWin32Api", Justification = "Using unmanaged equivalent for performance reasons")]
|
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SetCurrentDirectory")]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
internal static extern bool SetCurrentDirectoryWindows(string path);
|
|
|
|
internal static bool SetCurrentDirectory(string path)
|
|
{
|
|
if (IsWindows)
|
|
{
|
|
return SetCurrentDirectoryWindows(path);
|
|
}
|
|
|
|
// Make sure this does not throw
|
|
try
|
|
{
|
|
Directory.SetCurrentDirectory(path);
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
return true;
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
internal static unsafe extern int GetFullPathName(string target, int bufferLength, char* buffer, IntPtr mustBeZero);
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport("KERNEL32.DLL")]
|
|
private static extern SafeProcessHandle OpenProcess(eDesiredAccess dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId);
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport("NTDLL.DLL")]
|
|
private static extern int NtQueryInformationProcess(SafeProcessHandle hProcess, PROCESSINFOCLASS pic, ref PROCESS_BASIC_INFORMATION pbi, uint cb, ref int pSize);
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
[DllImport("kernel32.dll", CharSet = AutoOrUnicode, SetLastError = true)]
|
|
private static extern bool GlobalMemoryStatusEx([In, Out] MemoryStatus lpBuffer);
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, BestFitMapping = false)]
|
|
internal static extern int GetShortPathName(string path, [Out] StringBuilder fullpath, [In] int length);
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, BestFitMapping = false)]
|
|
internal static extern int GetLongPathName([In] string path, [Out] StringBuilder fullpath, [In] int length);
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport("kernel32.dll", CharSet = AutoOrUnicode, SetLastError = true)]
|
|
internal static extern bool CreatePipe(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, SecurityAttributes lpPipeAttributes, int nSize);
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport("kernel32.dll", CharSet = AutoOrUnicode, SetLastError = true)]
|
|
internal static extern bool ReadFile(SafeFileHandle hFile, byte[] lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped);
|
|
|
|
/// <summary>
|
|
/// CoWaitForMultipleHandles allows us to wait in an STA apartment and still service RPC requests from other threads.
|
|
/// VS needs this in order to allow the in-proc compilers to properly initialize, since they will make calls from the
|
|
/// build thread which the main thread (blocked on BuildSubmission.Execute) must service.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "Class name is NativeMethodsShared for increased clarity")]
|
|
[DllImport("ole32.dll")]
|
|
public static extern int CoWaitForMultipleHandles(COWAIT_FLAGS dwFlags, int dwTimeout, int cHandles, [MarshalAs(UnmanagedType.LPArray)] IntPtr[] pHandles, out int pdwIndex);
|
|
|
|
internal const uint GENERIC_READ = 0x80000000;
|
|
internal const uint FILE_SHARE_READ = 0x1;
|
|
internal const uint FILE_ATTRIBUTE_NORMAL = 0x80;
|
|
internal const uint FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000;
|
|
internal const uint OPEN_EXISTING = 3;
|
|
|
|
[DllImport("kernel32.dll", CharSet = AutoOrUnicode, CallingConvention = CallingConvention.StdCall,
|
|
SetLastError = true)]
|
|
internal static extern SafeFileHandle CreateFile(
|
|
string lpFileName,
|
|
uint dwDesiredAccess,
|
|
uint dwShareMode,
|
|
IntPtr lpSecurityAttributes,
|
|
uint dwCreationDisposition,
|
|
uint dwFlagsAndAttributes,
|
|
IntPtr hTemplateFile
|
|
);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
internal static extern bool GetFileTime(
|
|
SafeFileHandle hFile,
|
|
out FILETIME lpCreationTime,
|
|
out FILETIME lpLastAccessTime,
|
|
out FILETIME lpLastWriteTime
|
|
);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
|
|
internal static extern bool CloseHandle(IntPtr hObject);
|
|
|
|
#endregion
|
|
|
|
#region Extensions
|
|
|
|
/// <summary>
|
|
/// Waits while pumping APC messages. This is important if the waiting thread is an STA thread which is potentially
|
|
/// servicing COM calls from other threads.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Runtime.InteropServices.SafeHandle.DangerousGetHandle", Scope = "member", Target = "Microsoft.Build.Shared.NativeMethodsShared.#MsgWaitOne(System.Threading.WaitHandle,System.Int32)", Justification = "This is necessary and it has been used for a long time. No need to change it now.")]
|
|
internal static bool MsgWaitOne(this WaitHandle handle)
|
|
{
|
|
return handle.MsgWaitOne(Timeout.Infinite);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Waits while pumping APC messages. This is important if the waiting thread is an STA thread which is potentially
|
|
/// servicing COM calls from other threads.
|
|
/// </summary>
|
|
internal static bool MsgWaitOne(this WaitHandle handle, TimeSpan timeout)
|
|
{
|
|
return MsgWaitOne(handle, (int)timeout.TotalMilliseconds);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Waits while pumping APC messages. This is important if the waiting thread is an STA thread which is potentially
|
|
/// servicing COM calls from other threads.
|
|
/// </summary>
|
|
[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Runtime.InteropServices.SafeHandle.DangerousGetHandle", Justification = "Necessary to avoid pumping")]
|
|
internal static bool MsgWaitOne(this WaitHandle handle, int timeout)
|
|
{
|
|
// CoWaitForMultipleHandles allows us to wait in an STA apartment and still service RPC requests from other threads.
|
|
// VS needs this in order to allow the in-proc compilers to properly initialize, since they will make calls from the
|
|
// build thread which the main thread (blocked on BuildSubmission.Execute) must service.
|
|
int waitIndex;
|
|
#if FEATURE_HANDLE_SAFEWAITHANDLE
|
|
IntPtr handlePtr = handle.SafeWaitHandle.DangerousGetHandle();
|
|
#else
|
|
IntPtr handlePtr = handle.GetSafeWaitHandle().DangerousGetHandle();
|
|
#endif
|
|
int returnValue = CoWaitForMultipleHandles(COWAIT_FLAGS.COWAIT_NONE, timeout, 1, new IntPtr[] { handlePtr }, out waitIndex);
|
|
ErrorUtilities.VerifyThrow(returnValue == 0 || ((uint)returnValue == RPC_S_CALLPENDING && timeout != Timeout.Infinite), "Received {0} from CoWaitForMultipleHandles, but expected 0 (S_OK)", returnValue);
|
|
return returnValue == 0;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region helper methods
|
|
|
|
internal static bool DirectoryExists(string fullPath)
|
|
{
|
|
return NativeMethodsShared.IsWindows
|
|
? DirectoryExistsWindows(fullPath)
|
|
: Directory.Exists(fullPath);
|
|
}
|
|
|
|
internal static bool DirectoryExistsWindows(string fullPath)
|
|
{
|
|
NativeMethodsShared.WIN32_FILE_ATTRIBUTE_DATA data = new NativeMethodsShared.WIN32_FILE_ATTRIBUTE_DATA();
|
|
bool success = NativeMethodsShared.GetFileAttributesEx(fullPath, 0, ref data);
|
|
return success && (data.fileAttributes & NativeMethodsShared.FILE_ATTRIBUTE_DIRECTORY) != 0;
|
|
}
|
|
|
|
internal static bool FileExists(string fullPath)
|
|
{
|
|
return NativeMethodsShared.IsWindows
|
|
? FileExistsWindows(fullPath)
|
|
: File.Exists(fullPath);
|
|
}
|
|
|
|
internal static bool FileExistsWindows(string fullPath)
|
|
{
|
|
NativeMethodsShared.WIN32_FILE_ATTRIBUTE_DATA data = new NativeMethodsShared.WIN32_FILE_ATTRIBUTE_DATA();
|
|
bool success = NativeMethodsShared.GetFileAttributesEx(fullPath, 0, ref data);
|
|
return success && (data.fileAttributes & NativeMethodsShared.FILE_ATTRIBUTE_DIRECTORY) == 0;
|
|
}
|
|
|
|
internal static bool FileOrDirectoryExists(string path)
|
|
{
|
|
return IsWindows
|
|
? FileOrDirectoryExistsWindows(path)
|
|
: File.Exists(path) || Directory.Exists(path);
|
|
}
|
|
|
|
internal static bool FileOrDirectoryExistsWindows(string path)
|
|
{
|
|
WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA();
|
|
return GetFileAttributesEx(path, 0, ref data);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|