1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 //*****************************************************************************
9 // This module contains a set of functions that can be used to access the
12 //*****************************************************************************
21 #define COMPLUS_PREFIX W("COMPlus_")
22 #define LEN_OF_COMPLUS_PREFIX 8
24 #if (!defined(FEATURE_UTILCODE_NO_DEPENDENCIES) || defined(DEBUG)) && !defined(FEATURE_PAL)
25 #define ALLOW_REGISTRY
28 #undef WszRegCreateKeyEx
29 #undef WszRegOpenKeyEx
31 #define WszRegCreateKeyEx RegCreateKeyExW
32 #define WszRegOpenKeyEx RegOpenKeyExW
33 #define WszRegOpenKey(hKey, wszSubKey, phkRes) RegOpenKeyExW(hKey, wszSubKey, 0, KEY_ALL_ACCESS, phkRes)
35 //*****************************************************************************
36 // Reads from the environment setting
37 //*****************************************************************************
38 LPWSTR REGUTIL::EnvGetString(LPCWSTR name, BOOL fPrependCOMPLUS)
51 if(wcslen(name) > (size_t)(64 - 1 - (fPrependCOMPLUS ? LEN_OF_COMPLUS_PREFIX : 0)))
59 if (!EnvCacheValueNameSeenPerhaps(name))
62 #endif // ALLOW_REGISTRY
66 wcscpy_s(buff, _countof(buff), COMPLUS_PREFIX);
73 wcscat_s(buff, _countof(buff), name);
75 FAULT_NOT_FATAL(); // We don't report OOM errors here, we return a default value.
78 NewArrayHolder<WCHAR> ret = NULL;
85 Len = WszGetEnvironmentVariable(buff, temp);
88 ret = temp.GetCopyOfUnicodeString();
101 return ret.Extract();
109 #ifdef ALLOW_REGISTRY
112 #endif // ALLOW_REGISTRY
115 BOOL REGUTIL::UseRegistry()
117 #if !defined(ALLOW_REGISTRY)
120 return s_fUseRegistry;
124 //*****************************************************************************
125 // Reads a DWORD from the COR configuration according to the level specified
126 // Returns back defValue if the key cannot be found
127 //*****************************************************************************
128 DWORD REGUTIL::GetConfigDWORD_DontUse_(LPCWSTR name, DWORD defValue, CORConfigLevel level, BOOL fPrependCOMPLUS)
139 SUPPORTS_DAC_HOST_ONLY;
142 GetConfigInteger(name, defValue, &result, TRUE, level, fPrependCOMPLUS);
144 return (DWORD)result;
147 #define uniwcst(val, endptr, base) (fGetDWORD ? wcstoul(val, endptr, base) : _wcstoui64(val, endptr, base))
150 // Look up a dword config value, and write the result to the DWORD passed in by reference.
153 // * E_FAIL if the value is not found. (result is assigned the default value)
154 // * S_OK if the value is found. (result is assigned the value that was found)
157 // * info - see file:../inc/CLRConfig.h for details
158 // * result - Pointer to the output DWORD.
161 HRESULT REGUTIL::GetConfigDWORD_DontUse_(LPCWSTR name, DWORD defValue, __out DWORD * result, CORConfigLevel level, BOOL fPrependCOMPLUS)
164 HRESULT hr = GetConfigInteger(name, defValue, &ullResult, TRUE, level, fPrependCOMPLUS);
165 *result = (DWORD)ullResult;
169 ULONGLONG REGUTIL::GetConfigULONGLONG_DontUse_(LPCWSTR name, ULONGLONG defValue, CORConfigLevel level, BOOL fPrependCOMPLUS)
180 SUPPORTS_DAC_HOST_ONLY;
183 GetConfigInteger(name, defValue, &result, FALSE, level, fPrependCOMPLUS);
188 // This function should really be refactored to return the string from the environment and let the caller decide
189 // what to convert it to; and return the buffer read from the reg call.
190 // Note for PAL: right now PAL does not have a _wcstoui64 API, so I am temporarily reading in all numbers as
191 // a 32-bit number. When we have the _wcstoui64 API on MAC we will use uniwcst instead of wcstoul.
192 HRESULT REGUTIL::GetConfigInteger(LPCWSTR name, ULONGLONG defValue, __out ULONGLONG * result, BOOL fGetDWORD, CORConfigLevel level, BOOL fPrependCOMPLUS)
203 SUPPORTS_DAC_HOST_ONLY;
212 FAULT_NOT_FATAL(); // We don't report OOM errors here, we return a default value.
214 if (level & COR_CONFIG_ENV)
216 WCHAR* val = EnvGetString(name, fPrependCOMPLUS); // try getting it from the environement first
220 rtn = uniwcst(val, &endPtr, 16); // treat it has hex
221 BOOL fSuccess = ((errno != ERANGE) && (endPtr != val));
224 if (fSuccess) // success
232 // Early out if no registry access, simplifies following code.
234 if (!UseRegistry() || !(level & COR_CONFIG_REGISTRY))
240 #ifdef ALLOW_REGISTRY
241 // Probe the config cache to see if there is any point
242 // probing the registry; if not, don't bother.
244 if (!RegCacheValueNameSeenPerhaps(name))
249 #endif // ALLOW_REGISTRY
251 if (level & COR_CONFIG_USER)
253 #ifdef ALLOW_REGISTRY
255 LONG retVal = ERROR_SUCCESS;
256 BOOL bCloseHandle = FALSE;
257 userKey = s_hUserFrameworkKey;
259 if (userKey == INVALID_HANDLE_VALUE)
261 retVal = WszRegOpenKeyEx(HKEY_CURRENT_USER, FRAMEWORK_REGISTRY_KEY_W, 0, KEY_READ, &userKey);
265 if (retVal == ERROR_SUCCESS)
267 rtn = WszRegQueryValueEx(userKey, name, 0, &type, (LPBYTE)&ret, &size);
270 VERIFY(!RegCloseKey(userKey));
272 if (rtn == ERROR_SUCCESS && (type == REG_DWORD || (!fGetDWORD && type == REG_QWORD)))
279 #endif // ALLOW_REGISTRY
282 if (level & COR_CONFIG_MACHINE)
284 #ifdef ALLOW_REGISTRY
286 LONG retVal = ERROR_SUCCESS;
287 BOOL bCloseHandle = FALSE;
288 machineKey = s_hMachineFrameworkKey;
290 if (machineKey == INVALID_HANDLE_VALUE)
292 retVal = WszRegOpenKeyEx(HKEY_LOCAL_MACHINE, FRAMEWORK_REGISTRY_KEY_W, 0, KEY_READ, &machineKey);
296 if (retVal == ERROR_SUCCESS)
298 rtn = WszRegQueryValueEx(machineKey, name, 0, &type, (LPBYTE)&ret, &size);
301 VERIFY(!RegCloseKey(machineKey));
303 if (rtn == ERROR_SUCCESS && (type == REG_DWORD || (!fGetDWORD && type == REG_QWORD)))
310 #endif // ALLOW_REGISTRY
317 #define FUSION_REGISTRY_KEY_W W("Software\\Microsoft\\Fusion")
319 //*****************************************************************************
320 // Reads a string from the COR configuration according to the level specified
321 // The caller is responsible for deallocating the returned string by
322 // calling code:REGUTIL::FreeConfigString or using a code:ConfigStringHolder
323 //*****************************************************************************
325 LPWSTR REGUTIL::GetConfigString_DontUse_(LPCWSTR name, BOOL fPrependCOMPLUS, CORConfigLevel level, BOOL fUsePerfCache)
335 #ifdef ALLOW_REGISTRY
337 RegKeyHolder userKey = NULL;
338 RegKeyHolder machineKey = NULL;
339 RegKeyHolder fusionKey = NULL;
342 #endif // ALLOW_REGISTRY
343 NewArrayHolder<WCHAR> ret(NULL);
345 FAULT_NOT_FATAL(); // We don't report OOM errors here, we return a default value.
347 if (level & COR_CONFIG_ENV)
349 ret = EnvGetString(name, fPrependCOMPLUS); // try getting it from the environement first
353 ret.SuppressRelease();
360 // Early out if no registry access, simplifies following code.
362 if (!UseRegistry() || !(level & COR_CONFIG_REGISTRY))
367 #ifdef ALLOW_REGISTRY
368 // Probe the config cache to see if there is any point
369 // probing the registry; if not, don't bother.
371 if (fUsePerfCache && !RegCacheValueNameSeenPerhaps(name))
373 #endif // ALLOW_REGISTRY
375 if (level & COR_CONFIG_USER)
377 #ifdef ALLOW_REGISTRY
378 BOOL bUsingCachedKey = FALSE;
380 if (s_hUserFrameworkKey != INVALID_HANDLE_VALUE)
382 bUsingCachedKey = TRUE;
383 userKey = s_hUserFrameworkKey;
386 if (bUsingCachedKey || WszRegOpenKeyEx(HKEY_CURRENT_USER, FRAMEWORK_REGISTRY_KEY_W, 0, KEY_READ, &userKey) == ERROR_SUCCESS)
388 BOOL bReturn = FALSE;
389 if (WszRegQueryValueEx(userKey, name, 0, &type, 0, &size) == ERROR_SUCCESS &&
392 ret = (LPWSTR) new (nothrow) BYTE [size];
396 lResult = WszRegQueryValueEx(userKey, name, 0, 0, (LPBYTE) ret.GetValue(), &size);
397 _ASSERTE(lResult == ERROR_SUCCESS);
399 ret.SuppressRelease();
406 userKey.SuppressRelease();
412 #endif // ALLOW_REGISTRY
415 if (level & COR_CONFIG_MACHINE)
417 #ifdef ALLOW_REGISTRY
418 BOOL bUsingCachedKey = FALSE;
420 if (s_hMachineFrameworkKey != INVALID_HANDLE_VALUE)
422 bUsingCachedKey = TRUE;
423 machineKey = s_hMachineFrameworkKey;
426 if (bUsingCachedKey || WszRegOpenKeyEx(HKEY_LOCAL_MACHINE, FRAMEWORK_REGISTRY_KEY_W, 0, KEY_READ, &machineKey) == ERROR_SUCCESS)
428 BOOL bReturn = FALSE;
429 if (WszRegQueryValueEx(machineKey, name, 0, &type, 0, &size) == ERROR_SUCCESS &&
432 ret = (LPWSTR) new (nothrow) BYTE [size];
436 lResult = WszRegQueryValueEx(machineKey, name, 0, 0, (LPBYTE) ret.GetValue(), &size);
437 _ASSERTE(lResult == ERROR_SUCCESS);
439 ret.SuppressRelease();
446 machineKey.SuppressRelease();
452 #endif // ALLOW_REGISTRY
455 if (level & COR_CONFIG_FUSION)
457 #ifdef ALLOW_REGISTRY
458 if ((WszRegOpenKeyEx(HKEY_LOCAL_MACHINE, FUSION_REGISTRY_KEY_W, 0, KEY_READ, &fusionKey) == ERROR_SUCCESS) &&
459 (WszRegQueryValueEx(fusionKey, name, 0, &type, 0, &size) == ERROR_SUCCESS) &&
462 ret = (LPWSTR) new (nothrow) BYTE [size];
468 lResult = WszRegQueryValueEx(fusionKey, name, 0, 0, (LPBYTE) ret.GetValue(), &size);
469 _ASSERTE(lResult == ERROR_SUCCESS);
470 ret.SuppressRelease();
473 #endif // ALLOW_REGISTRY
479 //*****************************************************************************
480 // Deallocation function for code:REGUTIL::GetConfigString_DontUse_
483 // Use a code:ConfigStringHolder to automatically call this.
484 //*****************************************************************************
485 void REGUTIL::FreeConfigString(__in_z LPWSTR str)
487 LIMITED_METHOD_CONTRACT;
492 //*****************************************************************************
493 // Reads a BIT flag from the COR configuration according to the level specified
494 // Returns back defValue if the key cannot be found
495 //*****************************************************************************
496 DWORD REGUTIL::GetConfigFlag_DontUse_(LPCWSTR name, DWORD bitToSet, BOOL defValue)
500 return(GetConfigDWORD_DontUse_(name, defValue) != 0 ? bitToSet : 0);
504 #ifdef ALLOW_REGISTRY
509 // ProbabilisticNameSet:
511 // (Used by ConfigCache, below. If used elsewhere, might justify
512 // promotion to a standalone header file.)
514 // Represent a set of names in a small, fixed amount of storage.
515 // We turn a name into a small integer, then add the integer to a bitvector.
516 // An old trick we used in VC++4 minimal rebuild.
518 // For best results, the number of elements should be a fraction of
519 // the total number of bits in 'bits'.
521 // Note, only the const methods are thread-safe.
522 // Callers are responsible for providing their own synchronization when
523 // constructing and Add'ing names to the set.
525 class ProbabilisticNameSet {
527 ProbabilisticNameSet()
531 memset(bits, 0, sizeof(bits));
534 // Add a name to the set.
536 void Add(LPCWSTR name)
541 GetBitIndex(name, 0, &i, &mask);
545 void Add(LPCWSTR name, DWORD count)
550 GetBitIndex(name, count, &i, &mask);
554 // Return TRUE if a name *may have* been added to the set;
555 // return FALSE if the name *definitely* was NOT ever added to the set.
557 BOOL MayContain(LPCWSTR name) const
562 GetBitIndex(name, 0, &i, &mask);
563 return !!(bits[i] & mask);
567 static const unsigned cbitSet = 256U;
568 static const unsigned cbitWord = 8U*sizeof(unsigned);
569 unsigned bits[cbitSet/cbitWord];
571 // Return the word index and bit mask corresponding to the bitvector member
572 // addressed by the *case-insensitive* hash of the given name.
574 void GetBitIndex(LPCWSTR name, DWORD count, unsigned* pi, unsigned* pmask) const
576 LIMITED_METHOD_CONTRACT;
579 hash = HashiStringNKnownLower80(name, count) % cbitSet;
581 hash = HashiStringKnownLower80(name) % cbitSet;
582 *pi = hash / cbitWord;
583 *pmask = (1U << (hash % cbitWord));
589 // From the Win32 SDK docs:
590 // Registry Element Size Limits
592 // The maximum size of a value name is as follows:
593 // Windows Server 2003 and Windows XP: 16,383 characters
594 // Windows 2000: 260 ANSI characters or 16,383 Unicode characters.
595 // Windows Me/98/95: 255 characters
596 // Despite that, we only cache value names of 80 characters or less --
597 // longer names don't make sense as configuration settings names.
599 static const unsigned cchRegValueNameMax = 80;
601 BOOL REGUTIL::s_fUseRegCache = FALSE;
602 BOOL REGUTIL::s_fUseEnvCache = FALSE;
603 HKEY REGUTIL::s_hMachineFrameworkKey = (HKEY) INVALID_HANDLE_VALUE;
604 HKEY REGUTIL::s_hUserFrameworkKey = (HKEY) INVALID_HANDLE_VALUE;
605 BOOL REGUTIL::s_fUseRegistry = TRUE;
606 static ProbabilisticNameSet regNames; // set of registry value names seen; should be
607 // a static field of REGUTIL but I don't
608 // want to expose ProbabilisticNameSet.
609 static ProbabilisticNameSet envNames; // set of environment value names seen;
611 // "Registry Configuration Cache"
613 // Initialize the (optional) registry config cache.
615 // The purpose of the cache is to avoid hundreds of registry probes
616 // otherwise incurred by calls to GetConfigDWORD_DontUse_ and GetConfigString_DontUse_.
618 // We accomplish this by enumerating the relevant registry keys and
619 // remembering the extant value names; and then by avoiding probing
620 // for a name that was not seen in the enumeration (initialization) phase.
622 // It is optional in the sense that REGUTIL facilities like
623 // GetConfigDWORD_DontUse_ and GetConfigString_DontUse_ will work fine if the cache
624 // is never initialized; however, each config access then will hit
625 // the registry (typically multiple times to search HKCU and HKLM).
628 // Initialization: Enumerate these registry keys
629 // HKCU Software\Microsoft\.NetFramework
630 // HKLM Software\Microsoft\.NetFramework
631 // for value names, and "remember" them in the ProbalisticNameSet 'names'.
633 // If we ever find a reg value named DisableConfigCache under any of these
634 // three keys, the feature is disabled.
636 // This method is not thread-safe. It should only be called once.
638 // Perf Optimization for VSWhidbey:113373.
640 void REGUTIL::InitOptionalConfigCache()
649 static const HKEY roots[] = { HKEY_CURRENT_USER,
652 LONG l = ERROR_SUCCESS; // general Win32 API error return code
655 // No caching if the environment variable COMPlus_DisableConfigCache is set
657 if (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_DisableConfigCache) != 0)
660 // Enumerate each root
662 for (int i = 0; i < NumItems(roots); i++) {
663 hkey = NULL; // defensive
664 l = WszRegOpenKeyEx(roots[i], FRAMEWORK_REGISTRY_KEY_W, 0, KEY_READ, &hkey);
665 if (l == ERROR_FILE_NOT_FOUND) {
666 // That registry key is not present.
667 // For example, installation with no HKCU\...\.NETFramework.
668 // Should be OK to proceed.
671 #if defined(FEATURE_APPX) && !defined(DACCESS_COMPILE)
672 else if (l == ERROR_ACCESS_DENIED && AppX::IsAppXProcess()) {
673 // If we encounter access denied for the current key in AppX, ignore
674 // the failure and continue to cache the rest. Effectively this means
675 // we are caching that key as containing no values, which is correct
676 // because in the unlikely event there are values hiding underneath
677 // later attempts to access them (open the key) would also hit access
678 // denied and continue on probing other locations.
681 #endif // FEATURE_APPX && !DACCESS_COMPILE
682 else if (l != ERROR_SUCCESS) {
683 // Something else went wrong. To be safe, don't enable the cache.
687 // Enumerate every value name under this key.
689 for (int j = 0; ; j++) {
690 WCHAR wszValue[cchRegValueNameMax + 1];
691 DWORD dwValueSize = NumItems(wszValue);
692 l = WszRegEnumValue(hkey, j, wszValue, &dwValueSize,
693 NULL, NULL, NULL, NULL);
695 if (l == ERROR_SUCCESS) {
696 // Add value name to the names cache.
697 regNames.Add(wszValue);
699 else if (l == ERROR_NO_MORE_ITEMS) {
700 // Expected case: we've considered every value under this key.
703 else if ((l == ERROR_INSUFFICIENT_BUFFER || l == ERROR_MORE_DATA) &&
704 (dwValueSize > cchRegValueNameMax)) {
705 // Name is too long. That's OK, we don't cache such names.
708 #if defined(FEATURE_APPX) && !defined DACCESS_COMPILE
709 else if (l == ERROR_ACCESS_DENIED && AppX::IsAppXProcess()) {
710 // As above, ignore access denied in AppX and continue on trying to cache
713 #endif // FEATURE_APPX && !DACCESS_COMPILE
715 // WszRegEnumValue failed OOM, or something else went wrong.
716 // To be safe, don't enable the cache.
721 // Save the handles to framework regkeys so that future reads dont have to
723 if (roots[i] == HKEY_CURRENT_USER)
724 s_hUserFrameworkKey = hkey;
725 else if (roots[i] == HKEY_LOCAL_MACHINE)
726 s_hMachineFrameworkKey = hkey;
733 // Success. We've enumerated all value names under the roots;
734 // enable the REGUTIL value name config cache.
736 s_fUseRegCache = TRUE;
738 // Now create a cache of environment variables
739 if (WCHAR * wszStrings = WszGetEnvironmentStrings())
741 // GetEnvironmentStrings returns pointer to a null terminated block containing
742 // null terminated strings
743 for(WCHAR *wszCurr = wszStrings; *wszCurr; wszCurr++)
745 WCHAR wch = towlower(*wszCurr);
747 // Lets only cache env variables with the COMPlus prefix only
750 WCHAR *wszName = wszCurr;
752 // Look for the separator between name and value
753 while (*wszCurr && *wszCurr != W('='))
756 if (*wszCurr == W('='))
759 if(!SString::_wcsnicmp(wszName, COMPLUS_PREFIX, LEN_OF_COMPLUS_PREFIX))
761 wszName += LEN_OF_COMPLUS_PREFIX;
762 envNames.Add(wszName, (DWORD) (wszCurr - wszName));
767 // Look for current string termination
773 WszFreeEnvironmentStrings(wszStrings);
774 s_fUseEnvCache = TRUE;
784 // Return TRUE if the registry value name was seen (or might have been seen)
785 // in the registry at cache initialization time;
786 // return FALSE if it definitely was not seen at startup.
788 // If not using the config cache, return TRUE always.
790 // Perf Optimization for VSWhidbey:113373.
792 BOOL REGUTIL::RegCacheValueNameSeenPerhaps(LPCWSTR name)
796 return !s_fUseRegCache
797 || (wcslen(name) > cchRegValueNameMax)
798 || regNames.MayContain(name);
801 BOOL REGUTIL::EnvCacheValueNameSeenPerhaps(LPCWSTR name)
805 return !s_fUseEnvCache
806 || envNames.MayContain(name);
809 #endif // ALLOW_REGISTRY