368 lines
18 KiB
C#
368 lines
18 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="SharedUtils.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
namespace System.Diagnostics {
|
|
using System.Security.Permissions;
|
|
using System.Security;
|
|
using System.Threading;
|
|
using System.Text;
|
|
using Microsoft.Win32;
|
|
using System.Globalization;
|
|
using System.ComponentModel;
|
|
using System.Security.Principal;
|
|
using System.Security.AccessControl;
|
|
using System.Runtime.Versioning;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.ConstrainedExecution;
|
|
using System.Runtime.InteropServices;
|
|
using Microsoft.Win32.SafeHandles;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
|
|
internal static class SharedUtils {
|
|
|
|
internal const int UnknownEnvironment = 0;
|
|
internal const int W2kEnvironment = 1;
|
|
internal const int NtEnvironment = 2;
|
|
internal const int NonNtEnvironment = 3;
|
|
private static volatile int environment = UnknownEnvironment;
|
|
|
|
private static Object s_InternalSyncObject;
|
|
private static Object InternalSyncObject {
|
|
get {
|
|
if (s_InternalSyncObject == null) {
|
|
Object o = new Object();
|
|
Interlocked.CompareExchange(ref s_InternalSyncObject, o, null);
|
|
}
|
|
return s_InternalSyncObject;
|
|
}
|
|
}
|
|
|
|
internal static Win32Exception CreateSafeWin32Exception() {
|
|
return CreateSafeWin32Exception(0);
|
|
}
|
|
|
|
internal static Win32Exception CreateSafeWin32Exception(int error) {
|
|
Win32Exception newException = null;
|
|
// Need to assert SecurtiyPermission, otherwise Win32Exception
|
|
// will not be able to get the error message. At this point the right
|
|
// permissions have already been demanded.
|
|
SecurityPermission securityPermission = new SecurityPermission(PermissionState.Unrestricted);
|
|
securityPermission.Assert();
|
|
try {
|
|
if (error == 0)
|
|
newException = new Win32Exception();
|
|
else
|
|
newException = new Win32Exception(error);
|
|
}
|
|
finally {
|
|
SecurityPermission.RevertAssert();
|
|
}
|
|
|
|
return newException;
|
|
}
|
|
|
|
internal static int CurrentEnvironment {
|
|
get {
|
|
if (environment == UnknownEnvironment) {
|
|
lock (InternalSyncObject) {
|
|
if (environment == UnknownEnvironment) {
|
|
// Need to assert Environment permissions here
|
|
// the environment check is not exposed as a public method
|
|
if (Environment.OSVersion.Platform == PlatformID.Win32NT) {
|
|
if (Environment.OSVersion.Version.Major >= 5)
|
|
environment = W2kEnvironment;
|
|
else
|
|
environment = NtEnvironment;
|
|
}
|
|
else
|
|
environment = NonNtEnvironment;
|
|
}
|
|
}
|
|
}
|
|
|
|
return environment;
|
|
}
|
|
}
|
|
|
|
internal static void CheckEnvironment() {
|
|
if (CurrentEnvironment == NonNtEnvironment)
|
|
throw new PlatformNotSupportedException(SR.GetString(SR.WinNTRequired));
|
|
}
|
|
|
|
internal static void CheckNtEnvironment() {
|
|
if (CurrentEnvironment == NtEnvironment)
|
|
throw new PlatformNotSupportedException(SR.GetString(SR.Win2000Required));
|
|
}
|
|
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
internal static void EnterMutex(string name, ref Mutex mutex) {
|
|
string mutexName = null;
|
|
if (CurrentEnvironment == W2kEnvironment)
|
|
mutexName = "Global\\" + name;
|
|
else
|
|
mutexName = name;
|
|
|
|
EnterMutexWithoutGlobal(mutexName, ref mutex);
|
|
}
|
|
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
[SecurityPermission(SecurityAction.Assert, ControlPrincipal = true)]
|
|
[SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts", Justification = "[....]: We pass fixed data into sec.AddAccessRule")]
|
|
internal static void EnterMutexWithoutGlobal(string mutexName, ref Mutex mutex) {
|
|
bool createdNew;
|
|
MutexSecurity sec = new MutexSecurity();
|
|
SecurityIdentifier everyoneSid = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null);
|
|
sec.AddAccessRule(new MutexAccessRule(everyoneSid, MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Allow));
|
|
|
|
Mutex tmpMutex = new Mutex(false, mutexName, out createdNew, sec);
|
|
|
|
SafeWaitForMutex(tmpMutex, ref mutex);
|
|
}
|
|
|
|
// We need to atomically attempt to acquire the mutex and record whether we took it (because we require thread affinity
|
|
// while the mutex is held and the two states must be kept in lock step). We can get atomicity with a CER, but we don't want
|
|
// to hold a CER over a call to WaitOne (this could cause deadlocks). The ---- is to provide a new API out of
|
|
// mscorlib that performs the wait and lets us know if it succeeded. But at this late stage we don't want to expose a new
|
|
// API out of mscorlib, so we'll build our own solution.
|
|
// We'll P/Invoke out to the WaitForSingleObject inside a CER, but use a timeout to ensure we can't block a thread abort for
|
|
// an unlimited time (we do this in an infinite loop so the semantic of acquiring the mutex is unchanged, the timeout is
|
|
// just to allow us to poll for abort). A limitation of CERs in Whidbey (and part of the problem that put us in this
|
|
// position in the first place) is that a CER root in a method will cause the entire method to delay thread aborts. So we
|
|
// need to carefully partition the real CER part of out logic in a sub-method (and ensure the jit doesn't inline on us).
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
private static bool SafeWaitForMutex(Mutex mutexIn, ref Mutex mutexOut)
|
|
{
|
|
Debug.Assert(mutexOut == null, "You must pass in a null ref Mutex");
|
|
|
|
// Wait as long as necessary for the mutex.
|
|
while (true) {
|
|
|
|
// Attempt to acquire the mutex but timeout quickly if we can't.
|
|
if (!SafeWaitForMutexOnce(mutexIn, ref mutexOut))
|
|
return false;
|
|
if (mutexOut != null)
|
|
return true;
|
|
|
|
// We come out here to the outer method every so often so we're not in a CER and a thread abort can interrupt us.
|
|
// But the abort logic itself is poll based (in the this case) so we really need to check for a pending abort
|
|
// explicitly else the two timing windows will virtually never line up and we'll still end up stalling the abort
|
|
// attempt. Thread.Sleep checks for pending abort for us.
|
|
Thread.Sleep(0);
|
|
}
|
|
}
|
|
|
|
// The portion of SafeWaitForMutex that runs under a CER and thus must not block for a arbitrary period of time.
|
|
// This method must not be inlined (to stop the CER accidently spilling into the calling method).
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
[MethodImplAttribute(MethodImplOptions.NoInlining)]
|
|
private static bool SafeWaitForMutexOnce(Mutex mutexIn, ref Mutex mutexOut)
|
|
{
|
|
bool ret;
|
|
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
try {} finally {
|
|
// Wait for the mutex for half a second (long enough to gain the mutex in most scenarios and short enough to avoid
|
|
// impacting a thread abort for too long).
|
|
// Holding a mutex requires us to keep thread affinity and announce ourselves as a critical region.
|
|
Thread.BeginCriticalRegion();
|
|
Thread.BeginThreadAffinity();
|
|
int result = WaitForSingleObjectDontCallThis(mutexIn.SafeWaitHandle, 500);
|
|
switch (result) {
|
|
|
|
case NativeMethods.WAIT_OBJECT_0:
|
|
case NativeMethods.WAIT_ABANDONED:
|
|
// Mutex was obtained, atomically record that fact.
|
|
mutexOut = mutexIn;
|
|
ret = true;
|
|
break;
|
|
|
|
case NativeMethods.WAIT_TIMEOUT:
|
|
// Couldn't get mutex yet, simply return and we'll try again later.
|
|
ret = true;
|
|
break;
|
|
|
|
default:
|
|
// Some sort of failure return immediately all the way to the caller of SafeWaitForMutex.
|
|
ret = false;
|
|
break;
|
|
}
|
|
|
|
// If we're not leaving with the Mutex we don't require thread affinity and we're not a critical region any more.
|
|
if (mutexOut == null) {
|
|
Thread.EndThreadAffinity();
|
|
Thread.EndCriticalRegion();
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// P/Invoke for the methods above. Don't call this from anywhere else.
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
[System.Security.SuppressUnmanagedCodeSecurity]
|
|
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
|
|
[DllImport(ExternDll.Kernel32, ExactSpelling=true, SetLastError=true, EntryPoint="WaitForSingleObject")]
|
|
private static extern int WaitForSingleObjectDontCallThis(SafeWaitHandle handle, int timeout);
|
|
|
|
[ResourceExposure(ResourceScope.Machine)] // This is scoped to a Fx build dir.
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
// What if an app is locked back? Why would we use this?
|
|
internal static string GetLatestBuildDllDirectory(string machineName) {
|
|
string dllDir = "";
|
|
RegistryKey baseKey = null;
|
|
RegistryKey complusReg = null;
|
|
|
|
//This property is retrieved only when creationg a new category,
|
|
// the calling code already demanded PerformanceCounterPermission.
|
|
// Therefore the assert below is safe.
|
|
|
|
//This property is retrieved only when creationg a new log,
|
|
// the calling code already demanded EventLogPermission.
|
|
// Therefore the assert below is safe.
|
|
|
|
RegistryPermission registryPermission = new RegistryPermission(PermissionState.Unrestricted);
|
|
registryPermission.Assert();
|
|
|
|
try {
|
|
if (machineName.Equals(".")) {
|
|
return GetLocalBuildDirectory();
|
|
}
|
|
else {
|
|
baseKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, machineName);
|
|
}
|
|
if (baseKey == null)
|
|
throw new InvalidOperationException(SR.GetString(SR.RegKeyMissingShort, "HKEY_LOCAL_MACHINE", machineName));
|
|
|
|
complusReg = baseKey.OpenSubKey("SOFTWARE\\Microsoft\\.NETFramework");
|
|
if (complusReg != null) {
|
|
string installRoot = (string)complusReg.GetValue("InstallRoot");
|
|
if (installRoot != null && installRoot != String.Empty) {
|
|
// the "policy" subkey contains a v{major}.{minor} subkey for each version installed. There are also
|
|
// some extra subkeys like "standards" and "upgrades" we want to ignore.
|
|
|
|
// first we figure out what version we are...
|
|
string versionPrefix = "v" + Environment.Version.Major + "." + Environment.Version.Minor;
|
|
RegistryKey policyKey = complusReg.OpenSubKey("policy");
|
|
|
|
// This is the full version string of the install on the remote machine we want to use (for example "v2.0.50727")
|
|
string version = null;
|
|
|
|
if (policyKey != null) {
|
|
try {
|
|
|
|
// First check to see if there is a version of the runtime with the same minor and major number:
|
|
RegistryKey bestKey = policyKey.OpenSubKey(versionPrefix);
|
|
|
|
if (bestKey != null) {
|
|
try {
|
|
version = versionPrefix + "." + GetLargestBuildNumberFromKey(bestKey);
|
|
} finally {
|
|
bestKey.Close();
|
|
}
|
|
} else {
|
|
// There isn't an exact match for our version, so we will look for the largest version
|
|
// installed.
|
|
string[] majorVersions = policyKey.GetSubKeyNames();
|
|
int[] largestVersion = new int[] { -1, -1, -1 };
|
|
for (int i = 0; i < majorVersions.Length; i++) {
|
|
|
|
string majorVersion = majorVersions[i];
|
|
|
|
// If this looks like a key of the form v{something}.{something}, we should see if it's a usable build.
|
|
if (majorVersion.Length > 1 && majorVersion[0] == 'v' && majorVersion.Contains(".")) {
|
|
int[] currentVersion = new int[] { -1, -1, -1 };
|
|
|
|
string[] splitVersion = majorVersion.Substring(1).Split('.');
|
|
|
|
if(splitVersion.Length != 2) {
|
|
continue;
|
|
}
|
|
|
|
if (!Int32.TryParse(splitVersion[0], out currentVersion[0]) || !Int32.TryParse(splitVersion[1], out currentVersion[1])) {
|
|
continue;
|
|
}
|
|
|
|
RegistryKey k = policyKey.OpenSubKey(majorVersion);
|
|
if (k == null) {
|
|
// We may be able to use another subkey
|
|
continue;
|
|
}
|
|
try {
|
|
currentVersion[2] = GetLargestBuildNumberFromKey(k);
|
|
|
|
if (currentVersion[0] > largestVersion[0]
|
|
|| ((currentVersion[0] == largestVersion[0]) && (currentVersion[1] > largestVersion[1]))) {
|
|
largestVersion = currentVersion;
|
|
}
|
|
} finally {
|
|
k.Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
version = "v" + largestVersion[0] + "." + largestVersion[1] + "." + largestVersion[2];
|
|
}
|
|
} finally {
|
|
policyKey.Close();
|
|
}
|
|
|
|
if (version != null && version != String.Empty) {
|
|
StringBuilder installBuilder = new StringBuilder();
|
|
installBuilder.Append(installRoot);
|
|
if (!installRoot.EndsWith("\\", StringComparison.Ordinal))
|
|
installBuilder.Append("\\");
|
|
installBuilder.Append(version);
|
|
dllDir = installBuilder.ToString();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
// ignore
|
|
}
|
|
finally {
|
|
if (complusReg != null)
|
|
complusReg.Close();
|
|
|
|
if (baseKey != null)
|
|
baseKey.Close();
|
|
|
|
RegistryPermission.RevertAssert();
|
|
}
|
|
|
|
return dllDir;
|
|
}
|
|
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
private static int GetLargestBuildNumberFromKey(RegistryKey rootKey) {
|
|
int largestBuild = -1;
|
|
|
|
string[] minorVersions = rootKey.GetValueNames();
|
|
for (int i = 0; i < minorVersions.Length; i++) {
|
|
int o;
|
|
if (Int32.TryParse(minorVersions[i], out o)) {
|
|
largestBuild = (largestBuild > o) ? largestBuild : o;
|
|
}
|
|
}
|
|
|
|
return largestBuild;
|
|
}
|
|
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
private static string GetLocalBuildDirectory() {
|
|
return RuntimeEnvironment.GetRuntimeDirectory();
|
|
}
|
|
}
|
|
}
|