diff options
Diffstat (limited to 'src/mscorlib/src/Microsoft/Win32/RegistryKey.cs')
-rw-r--r-- | src/mscorlib/src/Microsoft/Win32/RegistryKey.cs | 672 |
1 files changed, 609 insertions, 63 deletions
diff --git a/src/mscorlib/src/Microsoft/Win32/RegistryKey.cs b/src/mscorlib/src/Microsoft/Win32/RegistryKey.cs index e39b95903e..521ea65e10 100644 --- a/src/mscorlib/src/Microsoft/Win32/RegistryKey.cs +++ b/src/mscorlib/src/Microsoft/Win32/RegistryKey.cs @@ -2,6 +2,28 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +/* + Note on transaction support: + Eventually we will want to add support for NT's transactions to our + RegistryKey API's (possibly Whidbey M3?). When we do this, here's + the list of API's we need to make transaction-aware: + + RegCreateKeyEx + RegDeleteKey + RegDeleteValue + RegEnumKeyEx + RegEnumValue + RegOpenKeyEx + RegQueryInfoKey + RegQueryValueEx + RegSetValueEx + + We can ignore RegConnectRegistry (remote registry access doesn't yet have + transaction support) and RegFlushKey. RegCloseKey doesn't require any + additional work. . + */ + /* Note on ACL support: The key thing to note about ACL's is you set them on a kernel object like a @@ -30,28 +52,34 @@ using Microsoft.Win32.SafeHandles; using System; using System.Buffers; using System.Collections.Generic; -using System.Diagnostics; +using System.Diagnostics.Contracts; using System.IO; +using System.Text; namespace Microsoft.Win32 { /** * Registry encapsulation. To get an instance of a RegistryKey use the * Registry class's static members then call OpenSubKey. + * + * @see Registry + * @security(checkDllCalls=off) + * @security(checkClassLinking=on) */ internal sealed class RegistryKey : MarshalByRefObject, IDisposable { - // Use the public Registry.CurrentUser - internal static readonly RegistryKey CurrentUser = - GetBaseKey(new IntPtr(unchecked((int)0x80000001)), "HKEY_CURRENT_USER"); - - // Use the public Registry.LocalMachine - internal static readonly RegistryKey LocalMachine = - GetBaseKey(new IntPtr(unchecked((int)0x80000002)), "HKEY_LOCAL_MACHINE"); - // We could use const here, if C# supported ELEMENT_TYPE_I fully. - private static readonly IntPtr HKEY_CURRENT_USER = new IntPtr(unchecked((int)0x80000001)); - private static readonly IntPtr HKEY_LOCAL_MACHINE = new IntPtr(unchecked((int)0x80000002)); + internal static readonly IntPtr HKEY_CLASSES_ROOT = new IntPtr(unchecked((int)0x80000000)); + internal static readonly IntPtr HKEY_CURRENT_USER = new IntPtr(unchecked((int)0x80000001)); + internal static readonly IntPtr HKEY_LOCAL_MACHINE = new IntPtr(unchecked((int)0x80000002)); + internal static readonly IntPtr HKEY_USERS = new IntPtr(unchecked((int)0x80000003)); + internal static readonly IntPtr HKEY_PERFORMANCE_DATA = new IntPtr(unchecked((int)0x80000004)); + internal static readonly IntPtr HKEY_CURRENT_CONFIG = new IntPtr(unchecked((int)0x80000005)); + + // Dirty indicates that we have munged data that should be potentially + // written to disk. + // + private const int STATE_DIRTY = 0x0001; // SystemKey indicates that this is a "SYSTEMKEY" and shouldn't be "opened" // or "closed". @@ -62,6 +90,20 @@ namespace Microsoft.Win32 // private const int STATE_WRITEACCESS = 0x0004; + // Indicates if this key is for HKEY_PERFORMANCE_DATA + private const int STATE_PERF_DATA = 0x0008; + + // Names of keys. This array must be in the same order as the HKEY values listed above. + // + private static readonly String[] hkeyNames = new String[] { + "HKEY_CLASSES_ROOT", + "HKEY_CURRENT_USER", + "HKEY_LOCAL_MACHINE", + "HKEY_USERS", + "HKEY_PERFORMANCE_DATA", + "HKEY_CURRENT_CONFIG", + }; + // MSDN defines the following limits for registry key names & values: // Key Name: 255 characters // Value name: 16,383 Unicode characters @@ -71,7 +113,31 @@ namespace Microsoft.Win32 private volatile SafeRegistryHandle hkey = null; private volatile int state = 0; - private volatile string keyName; + private volatile String keyName; + private volatile bool remoteKey = false; + private volatile RegistryKeyPermissionCheck checkMode; + private volatile RegistryView regView = RegistryView.Default; + + /** + * RegistryInternalCheck values. Useful only for CheckPermission + */ + private enum RegistryInternalCheck + { + CheckSubKeyWritePermission = 0, + CheckSubKeyReadPermission = 1, + CheckSubKeyCreatePermission = 2, + CheckSubTreeReadPermission = 3, + CheckSubTreeWritePermission = 4, + CheckSubTreeReadWritePermission = 5, + CheckValueWritePermission = 6, + CheckValueCreatePermission = 7, + CheckValueReadPermission = 8, + CheckKeyReadPermission = 9, + CheckSubTreePermission = 10, + CheckOpenSubKeyWithWritablePermission = 11, + CheckOpenSubKeyPermission = 12 + }; + /** * Creates a RegistryKey. @@ -82,10 +148,12 @@ namespace Microsoft.Win32 * The remoteKey flag when set to true indicates that we are dealing with registry entries * on a remote machine and requires the program making these calls to have full trust. */ - private RegistryKey(SafeRegistryHandle hkey, bool writable, bool systemkey) + private RegistryKey(SafeRegistryHandle hkey, bool writable, bool systemkey, bool remoteKey, bool isPerfData, RegistryView view) { this.hkey = hkey; keyName = ""; + this.remoteKey = remoteKey; + regView = view; if (systemkey) { state |= STATE_SYSTEMKEY; @@ -94,6 +162,17 @@ namespace Microsoft.Win32 { state |= STATE_WRITEACCESS; } + if (isPerfData) + state |= STATE_PERF_DATA; + ValidateKeyView(view); + } + + /** + * Closes this key, flushes it to disk if the contents have been modified. + */ + public void Close() + { + Dispose(true); } private void Dispose(bool disposing) @@ -115,6 +194,21 @@ namespace Microsoft.Win32 hkey = null; } } + else if (disposing && IsPerfDataKey()) + { + // System keys should never be closed. However, we want to call RegCloseKey + // on HKEY_PERFORMANCE_DATA when called from PerformanceCounter.CloseSharedResources + // (i.e. when disposing is true) so that we release the PERFLIB cache and cause it + // to be refreshed (by re-reading the registry) when accessed subsequently. + // This is the only way we can see the just installed perf counter. + // NOTE: since HKEY_PERFORMANCE_DATA is process wide, there is inherent race condition in closing + // the key asynchronously. While Vista is smart enough to rebuild the PERFLIB resources + // in this situation the down level OSes are not. We have a small window between + // the dispose below and usage elsewhere (other threads). This is By Design. + // This is less of an issue when OS > NT5 (i.e Vista & higher), we can close the perfkey + // (to release & refresh PERFLIB resources) and the OS will rebuild PERFLIB as necessary. + SafeRegistryHandle.RegCloseKey(RegistryKey.HKEY_PERFORMANCE_DATA); + } } } @@ -123,13 +217,13 @@ namespace Microsoft.Win32 Dispose(true); } - public void DeleteValue(string name, bool throwOnMissingValue) + public void DeleteValue(String name, bool throwOnMissingValue) { EnsureWriteable(); int errorCode = Win32Native.RegDeleteValue(hkey, name); // - // From windows 2003 server, if the name is too long we will get error code ERROR_FILENAME_EXCED_RANGE + // From windows 2003 server, if the name is too long we will get error code ERROR_FILENAME_EXCED_RANGE // This still means the name doesn't exist. We need to be consistent with previous OS. // if (errorCode == Win32Native.ERROR_FILE_NOT_FOUND || errorCode == Win32Native.ERROR_FILENAME_EXCED_RANGE) @@ -138,44 +232,77 @@ namespace Microsoft.Win32 { ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSubKeyValueAbsent); } - // Otherwise, just return giving no indication to the user. // (For compatibility) } - // We really should throw an exception here if errorCode was bad, // but we can't for compatibility reasons. - Debug.Assert(errorCode == 0, "RegDeleteValue failed. Here's your error code: " + errorCode); + BCLDebug.Correctness(errorCode == 0, "RegDeleteValue failed. Here's your error code: " + errorCode); + } + + /** + * Retrieves a new RegistryKey that represents the requested key. Valid + * values are: + * + * HKEY_CLASSES_ROOT, + * HKEY_CURRENT_USER, + * HKEY_LOCAL_MACHINE, + * HKEY_USERS, + * HKEY_PERFORMANCE_DATA, + * HKEY_CURRENT_CONFIG, + * HKEY_DYN_DATA. + * + * @param hKey HKEY_* to open. + * + * @return the RegistryKey requested. + */ + internal static RegistryKey GetBaseKey(IntPtr hKey) + { + return GetBaseKey(hKey, RegistryView.Default); } - private static RegistryKey GetBaseKey(IntPtr hKey, string keyName) + internal static RegistryKey GetBaseKey(IntPtr hKey, RegistryView view) { - SafeRegistryHandle srh = new SafeRegistryHandle(hKey, ownsHandle: false); + int index = ((int)hKey) & 0x0FFFFFFF; + BCLDebug.Assert(index >= 0 && index < hkeyNames.Length, "index is out of range!"); + BCLDebug.Assert((((int)hKey) & 0xFFFFFFF0) == 0x80000000, "Invalid hkey value!"); + + bool isPerf = hKey == HKEY_PERFORMANCE_DATA; + // only mark the SafeHandle as ownsHandle if the key is HKEY_PERFORMANCE_DATA. + SafeRegistryHandle srh = new SafeRegistryHandle(hKey, isPerf); - RegistryKey key = new RegistryKey(srh, true, true); - key.keyName = keyName; + RegistryKey key = new RegistryKey(srh, true, true, false, isPerf, view); + key.checkMode = RegistryKeyPermissionCheck.Default; + key.keyName = hkeyNames[index]; return key; } - /// <summary> - /// Retrieves a subkey or null if the operation failed. - /// </summary> - /// <param name="writable">True to open writable, otherwise opens the key read-only.</param> + /** + * Retrieves a subkey. If readonly is <b>true</b>, then the subkey is opened with + * read-only access. + * + * @param name Name or path of subkey to open. + * @param readonly Set to <b>true</b> if you only need readonly access. + * + * @return the Subkey requested, or <b>null</b> if the operation failed. + */ public RegistryKey OpenSubKey(string name, bool writable) { ValidateKeyName(name); EnsureNotDisposed(); + name = FixupName(name); // Fixup multiple slashes to a single slash SafeRegistryHandle result = null; int ret = Win32Native.RegOpenKeyEx(hkey, name, 0, - writable ? Win32Native.KEY_READ | Win32Native.KEY_WRITE : Win32Native.KEY_READ, + GetRegistryKeyAccess(writable) | (int)regView, out result); if (ret == 0 && !result.IsInvalid) { - RegistryKey key = new RegistryKey(result, writable, false); + RegistryKey key = new RegistryKey(result, writable, false, remoteKey, false, regView); + key.checkMode = GetSubKeyPermissonCheck(writable); key.keyName = keyName + "\\" + name; return key; } @@ -191,6 +318,41 @@ namespace Microsoft.Win32 return null; } + // This required no security checks. This is to get around the Deleting SubKeys which only require + // write permission. They call OpenSubKey which required read. Now instead call this function w/o security checks + internal RegistryKey InternalOpenSubKey(String name, bool writable) + { + ValidateKeyName(name); + EnsureNotDisposed(); + + SafeRegistryHandle result = null; + int ret = Win32Native.RegOpenKeyEx(hkey, + name, + 0, + GetRegistryKeyAccess(writable) | (int)regView, + out result); + + if (ret == 0 && !result.IsInvalid) + { + RegistryKey key = new RegistryKey(result, writable, false, remoteKey, false, regView); + key.keyName = keyName + "\\" + name; + return key; + } + return null; + } + + /** + * Returns a subkey with read only permissions. + * + * @param name Name or path of subkey to open. + * + * @return the Subkey requested, or <b>null</b> if the operation failed. + */ + public RegistryKey OpenSubKey(String name) + { + return OpenSubKey(name, false); + } + /// <summary> /// Retrieves an array of strings containing all the subkey names. /// </summary> @@ -319,6 +481,22 @@ namespace Microsoft.Win32 } /** + * Retrieves the specified value. <b>null</b> is returned if the value + * doesn't exist. + * + * Note that <var>name</var> can be null or "", at which point the + * unnamed or default value of this Registry key is returned, if any. + * + * @param name Name of value to retrieve. + * + * @return the data associated with the value. + */ + public Object GetValue(String name) + { + return InternalGetValue(name, null, false, true); + } + + /** * Retrieves the specified value. <i>defaultValue</i> is returned if the value doesn't exist. * * Note that <var>name</var> can be null or "", at which point the @@ -333,11 +511,30 @@ namespace Microsoft.Win32 * * @return the data associated with the value. */ - public object GetValue(string name, object defaultValue = null, bool doNotExpand = false) + public Object GetValue(String name, Object defaultValue) { - EnsureNotDisposed(); + return InternalGetValue(name, defaultValue, false, true); + } + + public Object GetValue(String name, Object defaultValue, RegistryValueOptions options) + { + if (options < RegistryValueOptions.None || options > RegistryValueOptions.DoNotExpandEnvironmentNames) + { + throw new ArgumentException(SR.Format(SR.Arg_EnumIllegalVal, (int)options), nameof(options)); + } + bool doNotExpand = (options == RegistryValueOptions.DoNotExpandEnvironmentNames); + return InternalGetValue(name, defaultValue, doNotExpand, true); + } + + internal Object InternalGetValue(String name, Object defaultValue, bool doNotExpand, bool checkSecurity) + { + if (checkSecurity) + { + // Name can be null! It's the most common use of RegQueryValueEx + EnsureNotDisposed(); + } - object data = defaultValue; + Object data = defaultValue; int type = 0; int datasize = 0; @@ -345,20 +542,54 @@ namespace Microsoft.Win32 if (ret != 0) { - // For stuff like ERROR_FILE_NOT_FOUND, we want to return null (data). - // Some OS's returned ERROR_MORE_DATA even in success cases, so we - // want to continue on through the function. - if (ret != Win32Native.ERROR_MORE_DATA) - return data; + if (IsPerfDataKey()) + { + int size = 65000; + int sizeInput = size; + + int r; + byte[] blob = new byte[size]; + while (Win32Native.ERROR_MORE_DATA == (r = Win32Native.RegQueryValueEx(hkey, name, null, ref type, blob, ref sizeInput))) + { + if (size == Int32.MaxValue) + { + // ERROR_MORE_DATA was returned however we cannot increase the buffer size beyond Int32.MaxValue + Win32Error(r, name); + } + else if (size > (Int32.MaxValue / 2)) + { + // at this point in the loop "size * 2" would cause an overflow + size = Int32.MaxValue; + } + else + { + size *= 2; + } + sizeInput = size; + blob = new byte[size]; + } + if (r != 0) + Win32Error(r, name); + return blob; + } + else + { + // For stuff like ERROR_FILE_NOT_FOUND, we want to return null (data). + // Some OS's returned ERROR_MORE_DATA even in success cases, so we + // want to continue on through the function. + if (ret != Win32Native.ERROR_MORE_DATA) + return data; + } } if (datasize < 0) { // unexpected code path - Debug.Assert(false, "[InternalGetValue] RegQueryValue returned ERROR_SUCCESS but gave a negative datasize"); + BCLDebug.Assert(false, "[InternalGetValue] RegQueryValue returned ERROR_SUCCESS but gave a negative datasize"); datasize = 0; } + switch (type) { case Win32Native.REG_NONE: @@ -378,7 +609,7 @@ namespace Microsoft.Win32 goto case Win32Native.REG_BINARY; } long blob = 0; - Debug.Assert(datasize == 8, "datasize==8"); + BCLDebug.Assert(datasize == 8, "datasize==8"); // Here, datasize must be 8 when calling this ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, ref blob, ref datasize); @@ -393,7 +624,7 @@ namespace Microsoft.Win32 goto case Win32Native.REG_QWORD; } int blob = 0; - Debug.Assert(datasize == 4, "datasize==4"); + BCLDebug.Assert(datasize == 4, "datasize==4"); // Here, datasize must be four when calling this ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, ref blob, ref datasize); @@ -420,13 +651,13 @@ namespace Microsoft.Win32 ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, blob, ref datasize); if (blob.Length > 0 && blob[blob.Length - 1] == (char)0) { - data = new string(blob, 0, blob.Length - 1); + data = new String(blob, 0, blob.Length - 1); } else { // in the very unlikely case the data is missing null termination, // pass in the whole char[] to prevent truncating a character - data = new string(blob); + data = new String(blob); } } break; @@ -451,17 +682,17 @@ namespace Microsoft.Win32 if (blob.Length > 0 && blob[blob.Length - 1] == (char)0) { - data = new string(blob, 0, blob.Length - 1); + data = new String(blob, 0, blob.Length - 1); } else { // in the very unlikely case the data is missing null termination, // pass in the whole char[] to prevent truncating a character - data = new string(blob); + data = new String(blob); } if (!doNotExpand) - data = Environment.ExpandEnvironmentVariables((string)data); + data = Environment.ExpandEnvironmentVariables((String)data); } break; case Win32Native.REG_MULTI_SZ: @@ -502,7 +733,8 @@ namespace Microsoft.Win32 blob[blob.Length - 1] = (char)0; } - IList<string> strings = new List<string>(); + + IList<String> strings = new List<String>(); int cur = 0; int len = blob.Length; @@ -516,28 +748,28 @@ namespace Microsoft.Win32 if (nextNull < len) { - Debug.Assert(blob[nextNull] == (char)0, "blob[nextNull] should be 0"); + BCLDebug.Assert(blob[nextNull] == (char)0, "blob[nextNull] should be 0"); if (nextNull - cur > 0) { - strings.Add(new string(blob, cur, nextNull - cur)); + strings.Add(new String(blob, cur, nextNull - cur)); } else { // we found an empty string. But if we're at the end of the data, // it's just the extra null terminator. if (nextNull != len - 1) - strings.Add(string.Empty); + strings.Add(String.Empty); } } else { - strings.Add(new string(blob, cur, len - cur)); + strings.Add(new String(blob, cur, len - cur)); } cur = nextNull + 1; } - data = new string[strings.Count]; - strings.CopyTo((string[])data, 0); + data = new String[strings.Count]; + strings.CopyTo((String[])data, 0); } break; case Win32Native.REG_LINK: @@ -560,10 +792,26 @@ namespace Microsoft.Win32 private bool IsPerfDataKey() { - return false; + return (state & STATE_PERF_DATA) != 0; + } + + private void SetDirty() + { + state |= STATE_DIRTY; + } + + /** + * Sets the specified value. + * + * @param name Name of value to store data in. + * @param value Data to store. + */ + public void SetValue(String name, Object value) + { + SetValue(name, value, RegistryValueKind.Unknown); } - public unsafe void SetStringValue(string name, string value) + public unsafe void SetValue(String name, Object value, RegistryValueKind valueKind) { if (value == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value); @@ -573,17 +821,167 @@ namespace Microsoft.Win32 throw new ArgumentException(SR.Arg_RegValStrLenBug); } + if (!Enum.IsDefined(typeof(RegistryValueKind), valueKind)) + throw new ArgumentException(SR.Arg_RegBadKeyKind, nameof(valueKind)); + EnsureWriteable(); - int result = Win32Native.RegSetValueEx(hkey, - name, - 0, - RegistryValueKind.String, - value, - checked(value.Length * 2 + 2)); + if (valueKind == RegistryValueKind.Unknown) + { + // this is to maintain compatibility with the old way of autodetecting the type. + // SetValue(string, object) will come through this codepath. + valueKind = CalculateValueKind(value); + } + + int ret = 0; + try + { + switch (valueKind) + { + case RegistryValueKind.ExpandString: + case RegistryValueKind.String: + { + String data = value.ToString(); + ret = Win32Native.RegSetValueEx(hkey, + name, + 0, + valueKind, + data, + checked(data.Length * 2 + 2)); + break; + } + + case RegistryValueKind.MultiString: + { + // Other thread might modify the input array after we calculate the buffer length. + // Make a copy of the input array to be safe. + string[] dataStrings = (string[])(((string[])value).Clone()); + int sizeInBytes = 0; + + // First determine the size of the array + // + for (int i = 0; i < dataStrings.Length; i++) + { + if (dataStrings[i] == null) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSetStrArrNull); + } + sizeInBytes = checked(sizeInBytes + (dataStrings[i].Length + 1) * 2); + } + sizeInBytes = checked(sizeInBytes + 2); + + byte[] basePtr = new byte[sizeInBytes]; + fixed (byte* b = basePtr) + { + IntPtr currentPtr = new IntPtr((void*)b); + + // Write out the strings... + // + for (int i = 0; i < dataStrings.Length; i++) + { + // Assumes that the Strings are always null terminated. + String.InternalCopy(dataStrings[i], currentPtr, (checked(dataStrings[i].Length * 2))); + currentPtr = new IntPtr((long)currentPtr + (checked(dataStrings[i].Length * 2))); + *(char*)(currentPtr.ToPointer()) = '\0'; + currentPtr = new IntPtr((long)currentPtr + 2); + } + + *(char*)(currentPtr.ToPointer()) = '\0'; + currentPtr = new IntPtr((long)currentPtr + 2); + + ret = Win32Native.RegSetValueEx(hkey, + name, + 0, + RegistryValueKind.MultiString, + basePtr, + sizeInBytes); + } + break; + } + + case RegistryValueKind.None: + case RegistryValueKind.Binary: + byte[] dataBytes = (byte[])value; + ret = Win32Native.RegSetValueEx(hkey, + name, + 0, + (valueKind == RegistryValueKind.None ? Win32Native.REG_NONE : RegistryValueKind.Binary), + dataBytes, + dataBytes.Length); + break; + + case RegistryValueKind.DWord: + { + // We need to use Convert here because we could have a boxed type cannot be + // unboxed and cast at the same time. I.e. ((int)(object)(short) 5) will fail. + int data = Convert.ToInt32(value, System.Globalization.CultureInfo.InvariantCulture); + + ret = Win32Native.RegSetValueEx(hkey, + name, + 0, + RegistryValueKind.DWord, + ref data, + 4); + break; + } - if (result != 0) - Win32Error(result, null); + case RegistryValueKind.QWord: + { + long data = Convert.ToInt64(value, System.Globalization.CultureInfo.InvariantCulture); + + ret = Win32Native.RegSetValueEx(hkey, + name, + 0, + RegistryValueKind.QWord, + ref data, + 8); + break; + } + } + } + catch (OverflowException) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSetMismatchedKind); + } + catch (InvalidOperationException) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSetMismatchedKind); + } + catch (FormatException) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSetMismatchedKind); + } + catch (InvalidCastException) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSetMismatchedKind); + } + + if (ret == 0) + { + SetDirty(); + } + else + Win32Error(ret, null); + } + + private RegistryValueKind CalculateValueKind(Object value) + { + // This logic matches what used to be in SetValue(string name, object value) in the v1.0 and v1.1 days. + // Even though we could add detection for an int64 in here, we want to maintain compatibility with the + // old behavior. + if (value is Int32) + return RegistryValueKind.DWord; + else if (value is Array) + { + if (value is byte[]) + return RegistryValueKind.Binary; + else if (value is String[]) + return RegistryValueKind.MultiString; + else + throw new ArgumentException(SR.Format(SR.Arg_RegSetBadArrType, value.GetType().Name)); + } + else + return RegistryValueKind.String; } /** @@ -591,7 +989,7 @@ namespace Microsoft.Win32 * * @return a string representing the key. */ - public override string ToString() + public override String ToString() { EnsureNotDisposed(); return keyName; @@ -604,7 +1002,7 @@ namespace Microsoft.Win32 * error, and depending on the error, insert a string into the message * gotten from the ResourceManager. */ - internal void Win32Error(int errorCode, string str) + internal void Win32Error(int errorCode, String str) { switch (errorCode) { @@ -613,6 +1011,27 @@ namespace Microsoft.Win32 throw new UnauthorizedAccessException(SR.Format(SR.UnauthorizedAccess_RegistryKeyGeneric_Key, str)); else throw new UnauthorizedAccessException(); + + case Win32Native.ERROR_INVALID_HANDLE: + /** + * For normal RegistryKey instances we dispose the SafeRegHandle and throw IOException. + * However, for HKEY_PERFORMANCE_DATA (on a local or remote machine) we avoid disposing the + * SafeRegHandle and only throw the IOException. This is to workaround reentrancy issues + * in PerformanceCounter.NextValue() where the API could throw {NullReference, ObjectDisposed, ArgumentNull}Exception + * on reentrant calls because of this error code path in RegistryKey + * + * Normally we'd make our caller synchronize access to a shared RegistryKey instead of doing something like this, + * however we shipped PerformanceCounter.NextValue() un-synchronized in v2.0RTM and customers have taken a dependency on + * this behavior (being able to simultaneously query multiple remote-machine counters on multiple threads, instead of + * having serialized access). + */ + if (!IsPerfDataKey()) + { + hkey.SetHandleAsInvalid(); + hkey = null; + } + goto default; + case Win32Native.ERROR_FILE_NOT_FOUND: throw new IOException(SR.Arg_RegKeyNotFound, errorCode); @@ -621,6 +1040,76 @@ namespace Microsoft.Win32 } } + internal static String FixupName(String name) + { + BCLDebug.Assert(name != null, "[FixupName]name!=null"); + if (name.IndexOf('\\') == -1) + return name; + + StringBuilder sb = new StringBuilder(name); + FixupPath(sb); + int temp = sb.Length - 1; + if (temp >= 0 && sb[temp] == '\\') // Remove trailing slash + sb.Length = temp; + return sb.ToString(); + } + + + private static void FixupPath(StringBuilder path) + { + Contract.Requires(path != null); + int length = path.Length; + bool fixup = false; + char markerChar = (char)0xFFFF; + + int i = 1; + while (i < length - 1) + { + if (path[i] == '\\') + { + i++; + while (i < length) + { + if (path[i] == '\\') + { + path[i] = markerChar; + i++; + fixup = true; + } + else + break; + } + } + i++; + } + + if (fixup) + { + i = 0; + int j = 0; + while (i < length) + { + if (path[i] == markerChar) + { + i++; + continue; + } + path[j] = path[i]; + i++; + j++; + } + path.Length += j - i; + } + } + + private bool ContainsRegistryValue(string name) + { + int type = 0; + int datasize = 0; + int retval = Win32Native.RegQueryValueEx(hkey, name, null, ref type, (byte[])null, ref datasize); + return retval == 0; + } + private void EnsureNotDisposed() { if (hkey == null) @@ -638,8 +1127,41 @@ namespace Microsoft.Win32 } } + private static int GetRegistryKeyAccess(bool isWritable) + { + int winAccess; + if (!isWritable) + { + winAccess = Win32Native.KEY_READ; + } + else + { + winAccess = Win32Native.KEY_READ | Win32Native.KEY_WRITE; + } + + return winAccess; + } + + private RegistryKeyPermissionCheck GetSubKeyPermissonCheck(bool subkeyWritable) + { + if (checkMode == RegistryKeyPermissionCheck.Default) + { + return checkMode; + } + + if (subkeyWritable) + { + return RegistryKeyPermissionCheck.ReadWriteSubTree; + } + else + { + return RegistryKeyPermissionCheck.ReadSubTree; + } + } + static private void ValidateKeyName(string name) { + Contract.Ensures(name != null); if (name == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.name); @@ -660,9 +1182,33 @@ namespace Microsoft.Win32 ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegKeyStrLenBug); } + static private void ValidateKeyView(RegistryView view) + { + if (view != RegistryView.Default && view != RegistryView.Registry32 && view != RegistryView.Registry64) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidRegistryViewCheck, ExceptionArgument.view); + } + } + // Win32 constants for error handling private const int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; private const int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; private const int FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000; } + + [Flags] + internal enum RegistryValueOptions + { + None = 0, + DoNotExpandEnvironmentNames = 1 + } + + // the name for this API is meant to mimic FileMode, which has similar values + + internal enum RegistryKeyPermissionCheck + { + Default = 0, + ReadSubTree = 1, + ReadWriteSubTree = 2 + } } |