5b664f60ca1730600c6e3ceb1b09eb7e82660205
[mono.git] / mcs / class / referencesource / System / services / monitoring / system / diagnosticts / SharedUtils.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="SharedUtils.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>                                                                
5 //------------------------------------------------------------------------------
6
7 namespace System.Diagnostics {
8     using System.Security.Permissions;
9     using System.Security;    
10     using System.Threading;
11     using System.Text;
12     using Microsoft.Win32;
13     using System.Globalization;
14     using System.ComponentModel;
15     using System.Security.Principal;
16     using System.Security.AccessControl;
17     using System.Runtime.Versioning;
18     using System.Runtime.CompilerServices;
19     using System.Runtime.ConstrainedExecution;
20     using System.Runtime.InteropServices;
21     using Microsoft.Win32.SafeHandles;
22     using System.Diagnostics.CodeAnalysis;
23     
24     internal static class SharedUtils {
25         
26         internal const int UnknownEnvironment = 0;
27         internal const int W2kEnvironment = 1;
28         internal const int NtEnvironment = 2;
29         internal const int NonNtEnvironment = 3;        
30         private static volatile int environment = UnknownEnvironment;                
31
32         private static Object s_InternalSyncObject;
33         private static Object InternalSyncObject {
34             get {
35                 if (s_InternalSyncObject == null) {
36                     Object o = new Object();
37                     Interlocked.CompareExchange(ref s_InternalSyncObject, o, null);
38                 }
39                 return s_InternalSyncObject;
40             }
41         }
42
43         internal static Win32Exception CreateSafeWin32Exception() {
44             return CreateSafeWin32Exception(0);
45         }
46
47         internal static Win32Exception CreateSafeWin32Exception(int error) {
48             Win32Exception newException = null;
49             // Need to assert SecurtiyPermission, otherwise Win32Exception
50             // will not be able to get the error message. At this point the right
51             // permissions have already been demanded.
52             SecurityPermission securityPermission = new SecurityPermission(PermissionState.Unrestricted);
53             securityPermission.Assert();
54             try {
55                 if (error == 0)
56                     newException = new Win32Exception();
57                 else
58                     newException = new Win32Exception(error);
59             }
60             finally {
61                 SecurityPermission.RevertAssert();
62             }
63
64             return newException;
65         }
66
67         internal static int CurrentEnvironment {
68             get {
69                 if (environment == UnknownEnvironment) { 
70                     lock (InternalSyncObject) {
71                         if (environment == UnknownEnvironment) {
72                             // Need to assert Environment permissions here
73                             // the environment check is not exposed as a public method                        
74                             if (Environment.OSVersion.Platform == PlatformID.Win32NT)  {
75                                 if (Environment.OSVersion.Version.Major >= 5)
76                                     environment = W2kEnvironment; 
77                                 else
78                                     environment = NtEnvironment; 
79                             }                                
80                             else                    
81                                 environment = NonNtEnvironment;
82                         }                
83                     }
84                 }
85             
86                 return environment;                        
87             }                
88         }               
89                         
90         internal static void CheckEnvironment() {            
91             if (CurrentEnvironment == NonNtEnvironment)
92                 throw new PlatformNotSupportedException(SR.GetString(SR.WinNTRequired));
93         }
94
95         internal static void CheckNtEnvironment() {            
96             if (CurrentEnvironment == NtEnvironment)
97                 throw new PlatformNotSupportedException(SR.GetString(SR.Win2000Required));
98         }
99         
100         [ResourceExposure(ResourceScope.Machine)]
101         [ResourceConsumption(ResourceScope.Machine)]
102         internal static void EnterMutex(string name, ref Mutex mutex) {
103             string mutexName = null;
104             if (CurrentEnvironment == W2kEnvironment)
105                 mutexName = "Global\\" +  name; 
106             else
107                 mutexName = name;
108
109             EnterMutexWithoutGlobal(mutexName, ref mutex);
110         }
111
112         [ResourceExposure(ResourceScope.Machine)]
113         [ResourceConsumption(ResourceScope.Machine)]
114         [SecurityPermission(SecurityAction.Assert, ControlPrincipal = true)]
115         [SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts", Justification = "Microsoft: We pass fixed data into sec.AddAccessRule")]
116         internal static void EnterMutexWithoutGlobal(string mutexName, ref Mutex mutex) {
117             bool createdNew;
118             MutexSecurity sec = new MutexSecurity();
119             SecurityIdentifier everyoneSid = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null);
120             sec.AddAccessRule(new MutexAccessRule(everyoneSid, MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Allow));
121
122             Mutex tmpMutex = new Mutex(false, mutexName, out createdNew, sec);
123
124             SafeWaitForMutex(tmpMutex, ref mutex);
125         }
126
127         // We need to atomically attempt to acquire the mutex and record whether we took it (because we require thread affinity
128         // 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
129         // to hold a CER over a call to WaitOne (this could cause deadlocks). The ---- is to provide a new API out of
130         // 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
131         // API out of mscorlib, so we'll build our own solution.
132         // 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
133         // an unlimited time (we do this in an infinite loop so the semantic of acquiring the mutex is unchanged, the timeout is
134         // just to allow us to poll for abort). A limitation of CERs in Whidbey (and part of the problem that put us in this
135         // position in the first place) is that a CER root in a method will cause the entire method to delay thread aborts. So we
136         // need to carefully partition the real CER part of out logic in a sub-method (and ensure the jit doesn't inline on us).
137         [ResourceExposure(ResourceScope.Machine)]
138         [ResourceConsumption(ResourceScope.Machine)]
139         private static bool SafeWaitForMutex(Mutex mutexIn, ref Mutex mutexOut)
140         {
141             Debug.Assert(mutexOut == null, "You must pass in a null ref Mutex");
142
143             // Wait as long as necessary for the mutex.
144             while (true) {
145
146                 // Attempt to acquire the mutex but timeout quickly if we can't.
147                 if (!SafeWaitForMutexOnce(mutexIn, ref mutexOut))
148                     return false;
149                 if (mutexOut != null)
150                     return true;
151
152                 // 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.
153                 // But the abort logic itself is poll based (in the this case) so we really need to check for a pending abort
154                 // explicitly else the two timing windows will virtually never line up and we'll still end up stalling the abort
155                 // attempt. Thread.Sleep checks for pending abort for us.
156                 Thread.Sleep(0);
157             }
158         }
159
160         // The portion of SafeWaitForMutex that runs under a CER and thus must not block for a arbitrary period of time.
161         // This method must not be inlined (to stop the CER accidently spilling into the calling method).
162         [ResourceExposure(ResourceScope.Machine)]
163         [ResourceConsumption(ResourceScope.Machine)]
164         [MethodImplAttribute(MethodImplOptions.NoInlining)]
165         private static bool SafeWaitForMutexOnce(Mutex mutexIn, ref Mutex mutexOut)
166         {
167             bool ret;
168
169             RuntimeHelpers.PrepareConstrainedRegions();
170             try {} finally {
171                 // Wait for the mutex for half a second (long enough to gain the mutex in most scenarios and short enough to avoid
172                 // impacting a thread abort for too long).
173                 // Holding a mutex requires us to keep thread affinity and announce ourselves as a critical region.
174                 Thread.BeginCriticalRegion();
175                 Thread.BeginThreadAffinity();
176                 int result = WaitForSingleObjectDontCallThis(mutexIn.SafeWaitHandle, 500);
177                 switch (result) {
178
179                 case NativeMethods.WAIT_OBJECT_0:
180                 case NativeMethods.WAIT_ABANDONED:
181                     // Mutex was obtained, atomically record that fact.
182                     mutexOut = mutexIn;
183                     ret = true;
184                     break;
185
186                 case NativeMethods.WAIT_TIMEOUT:
187                     // Couldn't get mutex yet, simply return and we'll try again later.
188                     ret = true;
189                     break;
190
191                 default:
192                     // Some sort of failure return immediately all the way to the caller of SafeWaitForMutex.
193                     ret = false;
194                     break;
195                 }
196
197                 // If we're not leaving with the Mutex we don't require thread affinity and we're not a critical region any more.
198                 if (mutexOut == null) {
199                     Thread.EndThreadAffinity();
200                     Thread.EndCriticalRegion();
201                 }
202             }
203
204             return ret;
205         }
206
207         // P/Invoke for the methods above. Don't call this from anywhere else.
208         [ResourceExposure(ResourceScope.Machine)]
209         [ResourceConsumption(ResourceScope.Machine)]
210         [System.Security.SuppressUnmanagedCodeSecurity]
211         [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
212         [DllImport(ExternDll.Kernel32, ExactSpelling=true, SetLastError=true, EntryPoint="WaitForSingleObject")]
213         private static extern int WaitForSingleObjectDontCallThis(SafeWaitHandle handle, int timeout);
214
215         [ResourceExposure(ResourceScope.Machine)]  // This is scoped to a Fx build dir.
216         [ResourceConsumption(ResourceScope.Machine)]
217             // What if an app is locked back?  Why would we use this?
218         internal static string GetLatestBuildDllDirectory(string machineName) {
219             string dllDir = "";
220             RegistryKey baseKey = null;
221             RegistryKey complusReg = null;
222             
223             //This property is retrieved only when creationg a new category,
224             //                          the calling code already demanded PerformanceCounterPermission.
225             //                          Therefore the assert below is safe.
226                                                 
227             //This property is retrieved only when creationg a new log,
228             //                          the calling code already demanded EventLogPermission.
229             //                          Therefore the assert below is safe.
230
231             RegistryPermission registryPermission = new RegistryPermission(PermissionState.Unrestricted);
232             registryPermission.Assert();
233
234             try {
235                 if (machineName.Equals(".")) {
236                     return GetLocalBuildDirectory();
237                 }
238                 else {
239                     baseKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, machineName);
240                 }
241                 if (baseKey == null)
242                     throw new InvalidOperationException(SR.GetString(SR.RegKeyMissingShort, "HKEY_LOCAL_MACHINE", machineName));
243
244                 complusReg = baseKey.OpenSubKey("SOFTWARE\\Microsoft\\.NETFramework");
245                 if (complusReg != null) {
246                     string installRoot = (string)complusReg.GetValue("InstallRoot");
247                     if (installRoot != null && installRoot != String.Empty) {
248                         // the "policy" subkey contains a v{major}.{minor} subkey for each version installed.  There are also
249                         // some extra subkeys like "standards" and "upgrades" we want to ignore.
250
251                         // first we figure out what version we are...
252                         string versionPrefix = "v" + Environment.Version.Major + "." + Environment.Version.Minor;
253                         RegistryKey policyKey = complusReg.OpenSubKey("policy");
254
255                         // This is the full version string of the install on the remote machine we want to use (for example "v2.0.50727")
256                         string version = null;
257
258                         if (policyKey != null) {
259                             try {
260                                 
261                                 // First check to see if there is a version of the runtime with the same minor and major number:
262                                 RegistryKey bestKey = policyKey.OpenSubKey(versionPrefix);
263
264                                 if (bestKey != null) {
265                                     try {
266                                         version = versionPrefix + "." + GetLargestBuildNumberFromKey(bestKey);
267                                     } finally {
268                                         bestKey.Close();
269                                     }
270                                 } else {
271                                     // There isn't an exact match for our version, so we will look for the largest version
272                                     // installed.
273                                     string[] majorVersions = policyKey.GetSubKeyNames();
274                                     int[] largestVersion = new int[] { -1, -1, -1 };
275                                     for (int i = 0; i < majorVersions.Length; i++) {
276
277                                         string majorVersion = majorVersions[i];
278
279                                         // If this looks like a key of the form v{something}.{something}, we should see if it's a usable build.
280                                         if (majorVersion.Length > 1 && majorVersion[0] == 'v' && majorVersion.Contains(".")) {
281                                             int[] currentVersion = new int[] { -1, -1, -1 };
282
283                                             string[] splitVersion = majorVersion.Substring(1).Split('.');
284
285                                             if(splitVersion.Length != 2) {
286                                                 continue;
287                                             }
288
289                                             if (!Int32.TryParse(splitVersion[0], out currentVersion[0]) || !Int32.TryParse(splitVersion[1], out currentVersion[1])) {
290                                                 continue;
291                                             }
292
293                                             RegistryKey k = policyKey.OpenSubKey(majorVersion);
294                                             if (k == null) {
295                                                 // We may be able to use another subkey
296                                                 continue;
297                                             }
298                                             try {
299                                                 currentVersion[2] = GetLargestBuildNumberFromKey(k);
300
301                                                 if (currentVersion[0] > largestVersion[0]
302                                                     || ((currentVersion[0] == largestVersion[0]) && (currentVersion[1] > largestVersion[1]))) {
303                                                     largestVersion = currentVersion;
304                                                 }
305                                             } finally {
306                                                 k.Close();
307                                             }
308                                         }
309                                     }
310
311                                     version = "v" + largestVersion[0] + "." + largestVersion[1] + "." + largestVersion[2];
312                                 }
313                             } finally {
314                                 policyKey.Close();
315                             }
316
317                             if (version != null && version != String.Empty) {
318                                 StringBuilder installBuilder = new StringBuilder();
319                                 installBuilder.Append(installRoot);
320                                 if (!installRoot.EndsWith("\\", StringComparison.Ordinal))
321                                     installBuilder.Append("\\");
322                                 installBuilder.Append(version);
323                                 dllDir = installBuilder.ToString();
324                             }
325                         }
326                     }
327                 }                                      
328             }
329             catch {
330                 // ignore
331             }
332             finally {
333                 if (complusReg != null)
334                     complusReg.Close();
335
336                 if (baseKey != null)
337                     baseKey.Close();
338
339                 RegistryPermission.RevertAssert();                             
340             }
341
342             return dllDir;
343         }                
344
345         [ResourceExposure(ResourceScope.Machine)]
346         [ResourceConsumption(ResourceScope.Machine)]
347         private static int GetLargestBuildNumberFromKey(RegistryKey rootKey) {
348             int largestBuild = -1;
349
350             string[] minorVersions = rootKey.GetValueNames();
351             for (int i = 0; i < minorVersions.Length; i++) {
352                 int o;
353                 if (Int32.TryParse(minorVersions[i], out o)) {
354                     largestBuild = (largestBuild > o) ? largestBuild : o;
355                 }
356             }
357
358             return largestBuild;
359         }
360
361         [ResourceExposure(ResourceScope.Machine)]
362         [ResourceConsumption(ResourceScope.Machine)]
363         private static string GetLocalBuildDirectory() {
364             return RuntimeEnvironment.GetRuntimeDirectory();
365         }
366     }
367 }