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)
52 if(wcslen(name) > (size_t)(64 - 1 - (fPrependCOMPLUS ? LEN_OF_COMPLUS_PREFIX : 0)))
60 if (!EnvCacheValueNameSeenPerhaps(name))
63 #endif // ALLOW_REGISTRY
67 wcscpy_s(buff, _countof(buff), COMPLUS_PREFIX);
74 wcscat_s(buff, _countof(buff), name);
76 FAULT_NOT_FATAL(); // We don't report OOM errors here, we return a default value.
79 NewArrayHolder<WCHAR> ret = NULL;
82 BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD(SetLastError(COR_E_STACKOVERFLOW); return NULL;)
87 Len = WszGetEnvironmentVariable(buff, temp);
90 ret = temp.GetCopyOfUnicodeString();
95 END_SO_INTOLERANT_CODE
104 return ret.Extract();
112 #ifdef ALLOW_REGISTRY
115 #endif // ALLOW_REGISTRY
118 BOOL REGUTIL::UseRegistry()
120 #if !defined(ALLOW_REGISTRY)
123 return s_fUseRegistry;
127 //*****************************************************************************
128 // Reads a DWORD from the COR configuration according to the level specified
129 // Returns back defValue if the key cannot be found
130 //*****************************************************************************
131 DWORD REGUTIL::GetConfigDWORD_DontUse_(LPCWSTR name, DWORD defValue, CORConfigLevel level, BOOL fPrependCOMPLUS)
143 SUPPORTS_DAC_HOST_ONLY;
146 GetConfigInteger(name, defValue, &result, TRUE, level, fPrependCOMPLUS);
148 return (DWORD)result;
151 #define uniwcst(val, endptr, base) (fGetDWORD ? wcstoul(val, endptr, base) : _wcstoui64(val, endptr, base))
154 // Look up a dword config value, and write the result to the DWORD passed in by reference.
157 // * E_FAIL if the value is not found. (result is assigned the default value)
158 // * S_OK if the value is found. (result is assigned the value that was found)
161 // * info - see file:../inc/CLRConfig.h for details
162 // * result - Pointer to the output DWORD.
165 HRESULT REGUTIL::GetConfigDWORD_DontUse_(LPCWSTR name, DWORD defValue, __out DWORD * result, CORConfigLevel level, BOOL fPrependCOMPLUS)
168 HRESULT hr = GetConfigInteger(name, defValue, &ullResult, TRUE, level, fPrependCOMPLUS);
169 *result = (DWORD)ullResult;
173 ULONGLONG REGUTIL::GetConfigULONGLONG_DontUse_(LPCWSTR name, ULONGLONG defValue, CORConfigLevel level, BOOL fPrependCOMPLUS)
185 SUPPORTS_DAC_HOST_ONLY;
188 GetConfigInteger(name, defValue, &result, FALSE, level, fPrependCOMPLUS);
193 // This function should really be refactored to return the string from the environment and let the caller decide
194 // what to convert it to; and return the buffer read from the reg call.
195 // Note for PAL: right now PAL does not have a _wcstoui64 API, so I am temporarily reading in all numbers as
196 // a 32-bit number. When we have the _wcstoui64 API on MAC we will use uniwcst instead of wcstoul.
197 HRESULT REGUTIL::GetConfigInteger(LPCWSTR name, ULONGLONG defValue, __out ULONGLONG * result, BOOL fGetDWORD, CORConfigLevel level, BOOL fPrependCOMPLUS)
209 SUPPORTS_DAC_HOST_ONLY;
218 FAULT_NOT_FATAL(); // We don't report OOM errors here, we return a default value.
220 if (level & COR_CONFIG_ENV)
222 WCHAR* val = EnvGetString(name, fPrependCOMPLUS); // try getting it from the environement first
226 rtn = uniwcst(val, &endPtr, 16); // treat it has hex
227 BOOL fSuccess = ((errno != ERANGE) && (endPtr != val));
230 if (fSuccess) // success
238 // Early out if no registry access, simplifies following code.
240 if (!UseRegistry() || !(level & COR_CONFIG_REGISTRY))
246 #ifdef ALLOW_REGISTRY
247 // Probe the config cache to see if there is any point
248 // probing the registry; if not, don't bother.
250 if (!RegCacheValueNameSeenPerhaps(name))
255 #endif // ALLOW_REGISTRY
257 if (level & COR_CONFIG_USER)
259 #ifdef ALLOW_REGISTRY
261 LONG retVal = ERROR_SUCCESS;
262 BOOL bCloseHandle = FALSE;
263 userKey = s_hUserFrameworkKey;
265 if (userKey == INVALID_HANDLE_VALUE)
267 retVal = WszRegOpenKeyEx(HKEY_CURRENT_USER, FRAMEWORK_REGISTRY_KEY_W, 0, KEY_READ, &userKey);
271 if (retVal == ERROR_SUCCESS)
273 rtn = WszRegQueryValueEx(userKey, name, 0, &type, (LPBYTE)&ret, &size);
276 VERIFY(!RegCloseKey(userKey));
278 if (rtn == ERROR_SUCCESS && (type == REG_DWORD || (!fGetDWORD && type == REG_QWORD)))
285 #endif // ALLOW_REGISTRY
288 if (level & COR_CONFIG_MACHINE)
290 #ifdef ALLOW_REGISTRY
292 LONG retVal = ERROR_SUCCESS;
293 BOOL bCloseHandle = FALSE;
294 machineKey = s_hMachineFrameworkKey;
296 if (machineKey == INVALID_HANDLE_VALUE)
298 retVal = WszRegOpenKeyEx(HKEY_LOCAL_MACHINE, FRAMEWORK_REGISTRY_KEY_W, 0, KEY_READ, &machineKey);
302 if (retVal == ERROR_SUCCESS)
304 rtn = WszRegQueryValueEx(machineKey, name, 0, &type, (LPBYTE)&ret, &size);
307 VERIFY(!RegCloseKey(machineKey));
309 if (rtn == ERROR_SUCCESS && (type == REG_DWORD || (!fGetDWORD && type == REG_QWORD)))
316 #endif // ALLOW_REGISTRY
323 #define FUSION_REGISTRY_KEY_W W("Software\\Microsoft\\Fusion")
325 //*****************************************************************************
326 // Reads a string from the COR configuration according to the level specified
327 // The caller is responsible for deallocating the returned string by
328 // calling code:REGUTIL::FreeConfigString or using a code:ConfigStringHolder
329 //*****************************************************************************
331 LPWSTR REGUTIL::GetConfigString_DontUse_(LPCWSTR name, BOOL fPrependCOMPLUS, CORConfigLevel level, BOOL fUsePerfCache)
341 #ifdef ALLOW_REGISTRY
343 RegKeyHolder userKey = NULL;
344 RegKeyHolder machineKey = NULL;
345 RegKeyHolder fusionKey = NULL;
348 #endif // ALLOW_REGISTRY
349 NewArrayHolder<WCHAR> ret(NULL);
351 FAULT_NOT_FATAL(); // We don't report OOM errors here, we return a default value.
353 if (level & COR_CONFIG_ENV)
355 ret = EnvGetString(name, fPrependCOMPLUS); // try getting it from the environement first
359 ret.SuppressRelease();
366 // Early out if no registry access, simplifies following code.
368 if (!UseRegistry() || !(level & COR_CONFIG_REGISTRY))
373 #ifdef ALLOW_REGISTRY
374 // Probe the config cache to see if there is any point
375 // probing the registry; if not, don't bother.
377 if (fUsePerfCache && !RegCacheValueNameSeenPerhaps(name))
379 #endif // ALLOW_REGISTRY
381 if (level & COR_CONFIG_USER)
383 #ifdef ALLOW_REGISTRY
384 BOOL bUsingCachedKey = FALSE;
386 if (s_hUserFrameworkKey != INVALID_HANDLE_VALUE)
388 bUsingCachedKey = TRUE;
389 userKey = s_hUserFrameworkKey;
392 if (bUsingCachedKey || WszRegOpenKeyEx(HKEY_CURRENT_USER, FRAMEWORK_REGISTRY_KEY_W, 0, KEY_READ, &userKey) == ERROR_SUCCESS)
394 BOOL bReturn = FALSE;
395 if (WszRegQueryValueEx(userKey, name, 0, &type, 0, &size) == ERROR_SUCCESS &&
398 ret = (LPWSTR) new (nothrow) BYTE [size];
402 lResult = WszRegQueryValueEx(userKey, name, 0, 0, (LPBYTE) ret.GetValue(), &size);
403 _ASSERTE(lResult == ERROR_SUCCESS);
405 ret.SuppressRelease();
412 userKey.SuppressRelease();
418 #endif // ALLOW_REGISTRY
421 if (level & COR_CONFIG_MACHINE)
423 #ifdef ALLOW_REGISTRY
424 BOOL bUsingCachedKey = FALSE;
426 if (s_hMachineFrameworkKey != INVALID_HANDLE_VALUE)
428 bUsingCachedKey = TRUE;
429 machineKey = s_hMachineFrameworkKey;
432 if (bUsingCachedKey || WszRegOpenKeyEx(HKEY_LOCAL_MACHINE, FRAMEWORK_REGISTRY_KEY_W, 0, KEY_READ, &machineKey) == ERROR_SUCCESS)
434 BOOL bReturn = FALSE;
435 if (WszRegQueryValueEx(machineKey, name, 0, &type, 0, &size) == ERROR_SUCCESS &&
438 ret = (LPWSTR) new (nothrow) BYTE [size];
442 lResult = WszRegQueryValueEx(machineKey, name, 0, 0, (LPBYTE) ret.GetValue(), &size);
443 _ASSERTE(lResult == ERROR_SUCCESS);
445 ret.SuppressRelease();
452 machineKey.SuppressRelease();
458 #endif // ALLOW_REGISTRY
461 if (level & COR_CONFIG_FUSION)
463 #ifdef ALLOW_REGISTRY
464 if ((WszRegOpenKeyEx(HKEY_LOCAL_MACHINE, FUSION_REGISTRY_KEY_W, 0, KEY_READ, &fusionKey) == ERROR_SUCCESS) &&
465 (WszRegQueryValueEx(fusionKey, name, 0, &type, 0, &size) == ERROR_SUCCESS) &&
468 ret = (LPWSTR) new (nothrow) BYTE [size];
474 lResult = WszRegQueryValueEx(fusionKey, name, 0, 0, (LPBYTE) ret.GetValue(), &size);
475 _ASSERTE(lResult == ERROR_SUCCESS);
476 ret.SuppressRelease();
479 #endif // ALLOW_REGISTRY
485 //*****************************************************************************
486 // Deallocation function for code:REGUTIL::GetConfigString_DontUse_
489 // Use a code:ConfigStringHolder to automatically call this.
490 //*****************************************************************************
491 void REGUTIL::FreeConfigString(__in_z LPWSTR str)
493 LIMITED_METHOD_CONTRACT;
498 //*****************************************************************************
499 // Reads a BIT flag from the COR configuration according to the level specified
500 // Returns back defValue if the key cannot be found
501 //*****************************************************************************
502 DWORD REGUTIL::GetConfigFlag_DontUse_(LPCWSTR name, DWORD bitToSet, BOOL defValue)
506 return(GetConfigDWORD_DontUse_(name, defValue) != 0 ? bitToSet : 0);
510 #ifdef ALLOW_REGISTRY
515 // ProbabilisticNameSet:
517 // (Used by ConfigCache, below. If used elsewhere, might justify
518 // promotion to a standalone header file.)
520 // Represent a set of names in a small, fixed amount of storage.
521 // We turn a name into a small integer, then add the integer to a bitvector.
522 // An old trick we used in VC++4 minimal rebuild.
524 // For best results, the number of elements should be a fraction of
525 // the total number of bits in 'bits'.
527 // Note, only the const methods are thread-safe.
528 // Callers are responsible for providing their own synchronization when
529 // constructing and Add'ing names to the set.
531 class ProbabilisticNameSet {
533 ProbabilisticNameSet()
537 memset(bits, 0, sizeof(bits));
540 // Add a name to the set.
542 void Add(LPCWSTR name)
547 GetBitIndex(name, 0, &i, &mask);
551 void Add(LPCWSTR name, DWORD count)
556 GetBitIndex(name, count, &i, &mask);
560 // Return TRUE if a name *may have* been added to the set;
561 // return FALSE if the name *definitely* was NOT ever added to the set.
563 BOOL MayContain(LPCWSTR name) const
568 GetBitIndex(name, 0, &i, &mask);
569 return !!(bits[i] & mask);
573 static const unsigned cbitSet = 256U;
574 static const unsigned cbitWord = 8U*sizeof(unsigned);
575 unsigned bits[cbitSet/cbitWord];
577 // Return the word index and bit mask corresponding to the bitvector member
578 // addressed by the *case-insensitive* hash of the given name.
580 void GetBitIndex(LPCWSTR name, DWORD count, unsigned* pi, unsigned* pmask) const
582 LIMITED_METHOD_CONTRACT;
585 hash = HashiStringNKnownLower80(name, count) % cbitSet;
587 hash = HashiStringKnownLower80(name) % cbitSet;
588 *pi = hash / cbitWord;
589 *pmask = (1U << (hash % cbitWord));
595 // From the Win32 SDK docs:
596 // Registry Element Size Limits
598 // The maximum size of a value name is as follows:
599 // Windows Server 2003 and Windows XP: 16,383 characters
600 // Windows 2000: 260 ANSI characters or 16,383 Unicode characters.
601 // Windows Me/98/95: 255 characters
602 // Despite that, we only cache value names of 80 characters or less --
603 // longer names don't make sense as configuration settings names.
605 static const unsigned cchRegValueNameMax = 80;
607 BOOL REGUTIL::s_fUseRegCache = FALSE;
608 BOOL REGUTIL::s_fUseEnvCache = FALSE;
609 HKEY REGUTIL::s_hMachineFrameworkKey = (HKEY) INVALID_HANDLE_VALUE;
610 HKEY REGUTIL::s_hUserFrameworkKey = (HKEY) INVALID_HANDLE_VALUE;
611 BOOL REGUTIL::s_fUseRegistry = TRUE;
612 static ProbabilisticNameSet regNames; // set of registry value names seen; should be
613 // a static field of REGUTIL but I don't
614 // want to expose ProbabilisticNameSet.
615 static ProbabilisticNameSet envNames; // set of environment value names seen;
617 // "Registry Configuration Cache"
619 // Initialize the (optional) registry config cache.
621 // The purpose of the cache is to avoid hundreds of registry probes
622 // otherwise incurred by calls to GetConfigDWORD_DontUse_ and GetConfigString_DontUse_.
624 // We accomplish this by enumerating the relevant registry keys and
625 // remembering the extant value names; and then by avoiding probing
626 // for a name that was not seen in the enumeration (initialization) phase.
628 // It is optional in the sense that REGUTIL facilities like
629 // GetConfigDWORD_DontUse_ and GetConfigString_DontUse_ will work fine if the cache
630 // is never initialized; however, each config access then will hit
631 // the registry (typically multiple times to search HKCU and HKLM).
634 // Initialization: Enumerate these registry keys
635 // HKCU Software\Microsoft\.NetFramework
636 // HKLM Software\Microsoft\.NetFramework
637 // for value names, and "remember" them in the ProbalisticNameSet 'names'.
639 // If we ever find a reg value named DisableConfigCache under any of these
640 // three keys, the feature is disabled.
642 // This method is not thread-safe. It should only be called once.
644 // Perf Optimization for VSWhidbey:113373.
646 void REGUTIL::InitOptionalConfigCache()
655 static const HKEY roots[] = { HKEY_CURRENT_USER,
658 LONG l = ERROR_SUCCESS; // general Win32 API error return code
661 // No caching if the environment variable COMPlus_DisableConfigCache is set
663 if (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_DisableConfigCache) != 0)
666 // Enumerate each root
668 for (int i = 0; i < NumItems(roots); i++) {
669 hkey = NULL; // defensive
670 l = WszRegOpenKeyEx(roots[i], FRAMEWORK_REGISTRY_KEY_W, 0, KEY_READ, &hkey);
671 if (l == ERROR_FILE_NOT_FOUND) {
672 // That registry key is not present.
673 // For example, installation with no HKCU\...\.NETFramework.
674 // Should be OK to proceed.
677 #if defined(FEATURE_APPX) && !defined(DACCESS_COMPILE)
678 else if (l == ERROR_ACCESS_DENIED && AppX::IsAppXProcess()) {
679 // If we encounter access denied for the current key in AppX, ignore
680 // the failure and continue to cache the rest. Effectively this means
681 // we are caching that key as containing no values, which is correct
682 // because in the unlikely event there are values hiding underneath
683 // later attempts to access them (open the key) would also hit access
684 // denied and continue on probing other locations.
687 #endif // FEATURE_APPX && !DACCESS_COMPILE
688 else if (l != ERROR_SUCCESS) {
689 // Something else went wrong. To be safe, don't enable the cache.
693 // Enumerate every value name under this key.
695 for (int j = 0; ; j++) {
696 WCHAR wszValue[cchRegValueNameMax + 1];
697 DWORD dwValueSize = NumItems(wszValue);
698 l = WszRegEnumValue(hkey, j, wszValue, &dwValueSize,
699 NULL, NULL, NULL, NULL);
701 if (l == ERROR_SUCCESS) {
702 // Add value name to the names cache.
703 regNames.Add(wszValue);
705 else if (l == ERROR_NO_MORE_ITEMS) {
706 // Expected case: we've considered every value under this key.
709 else if ((l == ERROR_INSUFFICIENT_BUFFER || l == ERROR_MORE_DATA) &&
710 (dwValueSize > cchRegValueNameMax)) {
711 // Name is too long. That's OK, we don't cache such names.
714 #if defined(FEATURE_APPX) && !defined DACCESS_COMPILE
715 else if (l == ERROR_ACCESS_DENIED && AppX::IsAppXProcess()) {
716 // As above, ignore access denied in AppX and continue on trying to cache
719 #endif // FEATURE_APPX && !DACCESS_COMPILE
721 // WszRegEnumValue failed OOM, or something else went wrong.
722 // To be safe, don't enable the cache.
727 // Save the handles to framework regkeys so that future reads dont have to
729 if (roots[i] == HKEY_CURRENT_USER)
730 s_hUserFrameworkKey = hkey;
731 else if (roots[i] == HKEY_LOCAL_MACHINE)
732 s_hMachineFrameworkKey = hkey;
739 // Success. We've enumerated all value names under the roots;
740 // enable the REGUTIL value name config cache.
742 s_fUseRegCache = TRUE;
744 // Now create a cache of environment variables
745 if (WCHAR * wszStrings = WszGetEnvironmentStrings())
747 // GetEnvironmentStrings returns pointer to a null terminated block containing
748 // null terminated strings
749 for(WCHAR *wszCurr = wszStrings; *wszCurr; wszCurr++)
751 WCHAR wch = towlower(*wszCurr);
753 // Lets only cache env variables with the COMPlus prefix only
756 WCHAR *wszName = wszCurr;
758 // Look for the separator between name and value
759 while (*wszCurr && *wszCurr != W('='))
762 if (*wszCurr == W('='))
765 if(!SString::_wcsnicmp(wszName, COMPLUS_PREFIX, LEN_OF_COMPLUS_PREFIX))
767 wszName += LEN_OF_COMPLUS_PREFIX;
768 envNames.Add(wszName, (DWORD) (wszCurr - wszName));
773 // Look for current string termination
779 WszFreeEnvironmentStrings(wszStrings);
780 s_fUseEnvCache = TRUE;
790 // Return TRUE if the registry value name was seen (or might have been seen)
791 // in the registry at cache initialization time;
792 // return FALSE if it definitely was not seen at startup.
794 // If not using the config cache, return TRUE always.
796 // Perf Optimization for VSWhidbey:113373.
798 BOOL REGUTIL::RegCacheValueNameSeenPerhaps(LPCWSTR name)
802 return !s_fUseRegCache
803 || (wcslen(name) > cchRegValueNameMax)
804 || regNames.MayContain(name);
807 BOOL REGUTIL::EnvCacheValueNameSeenPerhaps(LPCWSTR name)
811 return !s_fUseEnvCache
812 || envNames.MayContain(name);
815 #endif // ALLOW_REGISTRY