Jo Shields 3c1f479b9d Imported Upstream version 4.0.0~alpha1
Former-commit-id: 806294f5ded97629b74c85c09952f2a74fe182d9
2015-04-07 09:35:12 +01:00

1000 lines
26 KiB
C#

//
// Microsoft.Win32/UnixRegistryApi.cs
//
// Authors:
// Miguel de Icaza (miguel@gnome.org)
// Gert Driesen (drieseng@users.sourceforge.net)
//
// (C) 2005, 2006 Novell, Inc (http://www.novell.com)
//
// MISSING:
// It would be useful if we do case-insensitive expansion of variables,
// the registry is very windows specific, so we probably should default to
// those semantics in expanding environment variables, for example %path%
//
// We should use an ordered collection for storing the values (instead of
// a Hashtable).
//
// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if !NET_2_1
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Security;
using System.Threading;
using Microsoft.Win32.SafeHandles;
namespace Microsoft.Win32 {
class ExpandString {
string value;
public ExpandString (string s)
{
value = s;
}
public override string ToString ()
{
return value;
}
public string Expand ()
{
StringBuilder sb = new StringBuilder ();
for (int i = 0; i < value.Length; i++){
if (value [i] == '%'){
int j = i + 1;
for (; j < value.Length; j++){
if (value [j] == '%'){
string key = value.Substring (i + 1, j - i - 1);
sb.Append (Environment.GetEnvironmentVariable (key));
i += j;
break;
}
}
if (j == value.Length){
sb.Append ('%');
}
} else {
sb.Append (value [i]);
}
}
return sb.ToString ();
}
}
class KeyHandler
{
static Hashtable key_to_handler = new Hashtable ();
static Hashtable dir_to_handler = new Hashtable (
new CaseInsensitiveHashCodeProvider (), new CaseInsensitiveComparer ());
const string VolatileDirectoryName = "volatile-keys";
public string Dir;
string ActualDir; // Lets keep this one private.
public bool IsVolatile;
Hashtable values;
string file;
bool dirty;
static KeyHandler ()
{
CleanVolatileKeys ();
}
KeyHandler (RegistryKey rkey, string basedir) : this (rkey, basedir, false)
{
}
KeyHandler (RegistryKey rkey, string basedir, bool is_volatile)
{
// Force ourselved to reuse the key, if any.
string volatile_basedir = GetVolatileDir (basedir);
string actual_basedir = basedir;
if (Directory.Exists (basedir))
is_volatile = false;
else if (Directory.Exists (volatile_basedir)) {
actual_basedir = volatile_basedir;
is_volatile = true;
} else if (is_volatile)
actual_basedir = volatile_basedir;
if (!Directory.Exists (actual_basedir)) {
try {
Directory.CreateDirectory (actual_basedir);
} catch (UnauthorizedAccessException ex){
throw new SecurityException ("No access to the given key", ex);
}
}
Dir = basedir; // This is our identifier.
ActualDir = actual_basedir; // This our actual location.
IsVolatile = is_volatile;
file = Path.Combine (ActualDir, "values.xml");
Load ();
}
public void Load ()
{
values = new Hashtable ();
if (!File.Exists (file))
return;
try {
using (FileStream fs = File.OpenRead (file)){
StreamReader r = new StreamReader (fs);
string xml = r.ReadToEnd ();
if (xml.Length == 0)
return;
SecurityElement tree = SecurityElement.FromString (xml);
if (tree.Tag == "values" && tree.Children != null){
foreach (SecurityElement value in tree.Children){
if (value.Tag == "value"){
LoadKey (value);
}
}
}
}
} catch (UnauthorizedAccessException){
values.Clear ();
throw new SecurityException ("No access to the given key");
} catch (Exception e){
Console.Error.WriteLine ("While loading registry key at {0}: {1}", file, e);
values.Clear ();
}
}
void LoadKey (SecurityElement se)
{
Hashtable h = se.Attributes;
try {
string name = (string) h ["name"];
if (name == null)
return;
string type = (string) h ["type"];
if (type == null)
return;
switch (type){
case "int":
values [name] = Int32.Parse (se.Text);
break;
case "bytearray":
values [name] = Convert.FromBase64String (se.Text);
break;
case "string":
values [name] = se.Text == null ? String.Empty : se.Text;
break;
case "expand":
values [name] = new ExpandString (se.Text);
break;
case "qword":
values [name] = Int64.Parse (se.Text);
break;
case "string-array":
var sa = new List<string> ();
if (se.Children != null){
foreach (SecurityElement stre in se.Children){
sa.Add (stre.Text);
}
}
values [name] = sa.ToArray ();
break;
}
} catch {
// We ignore individual errors in the file.
}
}
public RegistryKey Ensure (RegistryKey rkey, string extra, bool writable)
{
return Ensure (rkey, extra, writable, false);
}
// 'is_volatile' is used only if the key hasn't been created already.
public RegistryKey Ensure (RegistryKey rkey, string extra, bool writable, bool is_volatile)
{
lock (typeof (KeyHandler)){
string f = Path.Combine (Dir, extra);
KeyHandler kh = (KeyHandler) dir_to_handler [f];
if (kh == null)
kh = new KeyHandler (rkey, f, is_volatile);
RegistryKey rk = new RegistryKey (kh, CombineName (rkey, extra), writable);
key_to_handler [rk] = kh;
dir_to_handler [f] = kh;
return rk;
}
}
public RegistryKey Probe (RegistryKey rkey, string extra, bool writable)
{
RegistryKey rk = null;
lock (typeof (KeyHandler)){
string f = Path.Combine (Dir, extra);
KeyHandler kh = (KeyHandler) dir_to_handler [f];
if (kh != null) {
rk = new RegistryKey (kh, CombineName (rkey,
extra), writable);
key_to_handler [rk] = kh;
} else if (Directory.Exists (f) || VolatileKeyExists (f)) {
kh = new KeyHandler (rkey, f);
rk = new RegistryKey (kh, CombineName (rkey, extra),
writable);
dir_to_handler [f] = kh;
key_to_handler [rk] = kh;
}
return rk;
}
}
static string CombineName (RegistryKey rkey, string extra)
{
if (extra.IndexOf ('/') != -1)
extra = extra.Replace ('/', '\\');
return String.Concat (rkey.Name, "\\", extra);
}
static long GetSystemBootTime ()
{
if (!File.Exists ("/proc/stat"))
return -1;
string btime = null;
string line;
try {
using (StreamReader stat_file = new StreamReader ("/proc/stat", Encoding.ASCII)) {
while ((line = stat_file.ReadLine ()) != null)
if (line.StartsWith ("btime")) {
btime = line;
break;
}
}
} catch (Exception e) {
Console.Error.WriteLine ("While reading system info {0}", e);
}
if (btime == null)
return -1;
int space = btime.IndexOf (' ');
long res;
if (!Int64.TryParse (btime.Substring (space, btime.Length - space), out res))
return -1;
return res;
}
// The registered boot time it's a simple line containing the last system btime.
static long GetRegisteredBootTime (string path)
{
if (!File.Exists (path))
return -1;
string line = null;
try {
using (StreamReader reader = new StreamReader (path, Encoding.ASCII))
line = reader.ReadLine ();
} catch (Exception e) {
Console.Error.WriteLine ("While reading registry data at {0}: {1}", path, e);
}
if (line == null)
return -1;
long res;
if (!Int64.TryParse (line, out res))
return -1;
return res;
}
static void SaveRegisteredBootTime (string path, long btime)
{
try {
using (StreamWriter writer = new StreamWriter (path, false, Encoding.ASCII))
writer.WriteLine (btime.ToString ());
} catch (Exception) {
/* This can happen when a user process tries to write to MachineStore */
//Console.Error.WriteLine ("While saving registry data at {0}: {1}", path, e);
}
}
// We save the last boot time in a last-btime file in every root, and we use it
// to clean the volatile keys directory in case the system btime changed.
static void CleanVolatileKeys ()
{
long system_btime = GetSystemBootTime ();
string [] roots = new string [] {
UserStore,
MachineStore
};
foreach (string root in roots) {
if (!Directory.Exists (root))
continue;
string btime_file = Path.Combine (root, "last-btime");
string volatile_dir = Path.Combine (root, VolatileDirectoryName);
if (Directory.Exists (volatile_dir)) {
long registered_btime = GetRegisteredBootTime (btime_file);
if (system_btime < 0 || registered_btime < 0 || registered_btime != system_btime)
Directory.Delete (volatile_dir, true);
}
SaveRegisteredBootTime (btime_file, system_btime);
}
}
public static bool VolatileKeyExists (string dir)
{
lock (typeof (KeyHandler)) {
KeyHandler kh = (KeyHandler) dir_to_handler [dir];
if (kh != null)
return kh.IsVolatile;
}
if (Directory.Exists (dir)) // Non-volatile key exists.
return false;
return Directory.Exists (GetVolatileDir (dir));
}
public static string GetVolatileDir (string dir)
{
string root = GetRootFromDir (dir);
string volatile_dir = dir.Replace (root, Path.Combine (root, VolatileDirectoryName));
return volatile_dir;
}
public static KeyHandler Lookup (RegistryKey rkey, bool createNonExisting)
{
lock (typeof (KeyHandler)){
KeyHandler k = (KeyHandler) key_to_handler [rkey];
if (k != null)
return k;
// when a non-root key is requested for no keyhandler exist
// then that key must have been marked for deletion
if (!rkey.IsRoot || !createNonExisting)
return null;
RegistryHive x = (RegistryHive) rkey.Hive;
switch (x){
case RegistryHive.CurrentUser:
string userDir = Path.Combine (UserStore, x.ToString ());
k = new KeyHandler (rkey, userDir);
dir_to_handler [userDir] = k;
break;
case RegistryHive.CurrentConfig:
case RegistryHive.ClassesRoot:
case RegistryHive.DynData:
case RegistryHive.LocalMachine:
case RegistryHive.PerformanceData:
case RegistryHive.Users:
string machine_dir = Path.Combine (MachineStore, x.ToString ());
k = new KeyHandler (rkey, machine_dir);
dir_to_handler [machine_dir] = k;
break;
default:
throw new Exception ("Unknown RegistryHive");
}
key_to_handler [rkey] = k;
return k;
}
}
static string GetRootFromDir (string dir)
{
if (dir.IndexOf (UserStore) > -1)
return UserStore;
else if (dir.IndexOf (MachineStore) > -1)
return MachineStore;
throw new Exception ("Could not get root for dir " + dir);
}
public static void Drop (RegistryKey rkey)
{
lock (typeof (KeyHandler)) {
KeyHandler k = (KeyHandler) key_to_handler [rkey];
if (k == null)
return;
key_to_handler.Remove (rkey);
// remove cached KeyHandler if no other keys reference it
int refCount = 0;
foreach (DictionaryEntry de in key_to_handler)
if (de.Value == k)
refCount++;
if (refCount == 0)
dir_to_handler.Remove (k.Dir);
}
}
public static void Drop (string dir)
{
lock (typeof (KeyHandler)) {
KeyHandler kh = (KeyHandler) dir_to_handler [dir];
if (kh == null)
return;
dir_to_handler.Remove (dir);
// remove (other) references to keyhandler
ArrayList keys = new ArrayList ();
foreach (DictionaryEntry de in key_to_handler)
if (de.Value == kh)
keys.Add (de.Key);
foreach (object key in keys)
key_to_handler.Remove (key);
}
}
public static bool Delete (string dir)
{
if (!Directory.Exists (dir)) {
string volatile_dir = GetVolatileDir (dir);
if (!Directory.Exists (volatile_dir))
return false;
dir = volatile_dir;
}
Directory.Delete (dir, true);
Drop (dir);
return true;
}
public RegistryValueKind GetValueKind (string name)
{
if (name == null)
return RegistryValueKind.Unknown;
object value = values [name];
if (value == null)
return RegistryValueKind.Unknown;
if (value is int)
return RegistryValueKind.DWord;
if (value is string [])
return RegistryValueKind.MultiString;
if (value is long)
return RegistryValueKind.QWord;
if (value is byte [])
return RegistryValueKind.Binary;
if (value is string)
return RegistryValueKind.String;
if (value is ExpandString)
return RegistryValueKind.ExpandString;
return RegistryValueKind.Unknown;
}
public object GetValue (string name, RegistryValueOptions options)
{
if (IsMarkedForDeletion)
return null;
if (name == null)
name = string.Empty;
object value = values [name];
ExpandString exp = value as ExpandString;
if (exp == null)
return value;
if ((options & RegistryValueOptions.DoNotExpandEnvironmentNames) == 0)
return exp.Expand ();
return exp.ToString ();
}
public void SetValue (string name, object value)
{
AssertNotMarkedForDeletion ();
if (name == null)
name = string.Empty;
// immediately convert non-native registry values to string to avoid
// returning it unmodified in calls to UnixRegistryApi.GetValue
if (value is int || value is string || value is byte[] || value is string[])
values[name] = value;
else
values[name] = value.ToString ();
SetDirty ();
}
public string [] GetValueNames ()
{
AssertNotMarkedForDeletion ();
ICollection keys = values.Keys;
string [] vals = new string [keys.Count];
keys.CopyTo (vals, 0);
return vals;
}
public int GetSubKeyCount ()
{
return GetSubKeyNames ().Length;
}
public string [] GetSubKeyNames ()
{
DirectoryInfo selfDir = new DirectoryInfo (ActualDir);
DirectoryInfo[] subDirs = selfDir.GetDirectories ();
string[] subKeyNames;
// for volatile keys (cannot contain non-volatile subkeys) or keys
// without *any* presence in the volatile key section, we can do it simple.
if (IsVolatile || !Directory.Exists (GetVolatileDir (Dir))) {
subKeyNames = new string[subDirs.Length];
for (int i = 0; i < subDirs.Length; i++) {
DirectoryInfo subDir = subDirs[i];
subKeyNames[i] = subDir.Name;
}
return subKeyNames;
}
// We may have the entries repeated, so keep just one of each one.
DirectoryInfo volatileDir = new DirectoryInfo (GetVolatileDir (Dir));
DirectoryInfo [] volatileSubDirs = volatileDir.GetDirectories ();
Dictionary<string,string> dirs = new Dictionary<string,string> ();
foreach (DirectoryInfo dir in subDirs)
dirs [dir.Name] = dir.Name;
foreach (DirectoryInfo volDir in volatileSubDirs)
dirs [volDir.Name] = volDir.Name;
subKeyNames = new string [dirs.Count];
int j = 0;
foreach (KeyValuePair<string,string> entry in dirs)
subKeyNames[j++] = entry.Value;
return subKeyNames;
}
//
// This version has to do argument validation based on the valueKind
//
public void SetValue (string name, object value, RegistryValueKind valueKind)
{
SetDirty ();
if (name == null)
name = string.Empty;
switch (valueKind){
case RegistryValueKind.String:
if (value is string){
values [name] = value;
return;
}
break;
case RegistryValueKind.ExpandString:
if (value is string){
values [name] = new ExpandString ((string)value);
return;
}
break;
case RegistryValueKind.Binary:
if (value is byte []){
values [name] = value;
return;
}
break;
case RegistryValueKind.DWord:
try {
values [name] = Convert.ToInt32 (value);
return;
} catch (OverflowException) {
break;
}
case RegistryValueKind.MultiString:
if (value is string []){
values [name] = value;
return;
}
break;
case RegistryValueKind.QWord:
try {
values [name] = Convert.ToInt64 (value);
return;
} catch (OverflowException) {
break;
}
default:
throw new ArgumentException ("unknown value", "valueKind");
}
throw new ArgumentException ("Value could not be converted to specified type", "valueKind");
}
void SetDirty ()
{
lock (typeof (KeyHandler)){
if (dirty)
return;
dirty = true;
new Timer (DirtyTimeout, null, 3000, Timeout.Infinite);
}
}
public void DirtyTimeout (object state)
{
Flush ();
}
public void Flush ()
{
lock (typeof (KeyHandler)) {
if (dirty) {
Save ();
dirty = false;
}
}
}
public bool ValueExists (string name)
{
if (name == null)
name = string.Empty;
return values.Contains (name);
}
public int ValueCount {
get {
return values.Keys.Count;
}
}
public bool IsMarkedForDeletion {
get {
return !dir_to_handler.Contains (Dir);
}
}
public void RemoveValue (string name)
{
AssertNotMarkedForDeletion ();
values.Remove (name);
SetDirty ();
}
~KeyHandler ()
{
Flush ();
}
void Save ()
{
if (IsMarkedForDeletion)
return;
if (!File.Exists (file) && values.Count == 0)
return;
SecurityElement se = new SecurityElement ("values");
// With SecurityElement.Text = value, and SecurityElement.AddAttribute(key, value)
// the values must be escaped prior to being assigned.
foreach (DictionaryEntry de in values){
object val = de.Value;
SecurityElement value = new SecurityElement ("value");
value.AddAttribute ("name", SecurityElement.Escape ((string) de.Key));
if (val is string){
value.AddAttribute ("type", "string");
value.Text = SecurityElement.Escape ((string) val);
} else if (val is int){
value.AddAttribute ("type", "int");
value.Text = val.ToString ();
} else if (val is long) {
value.AddAttribute ("type", "qword");
value.Text = val.ToString ();
} else if (val is byte []){
value.AddAttribute ("type", "bytearray");
value.Text = Convert.ToBase64String ((byte[]) val);
} else if (val is ExpandString){
value.AddAttribute ("type", "expand");
value.Text = SecurityElement.Escape (val.ToString ());
} else if (val is string []){
value.AddAttribute ("type", "string-array");
foreach (string ss in (string[]) val){
SecurityElement str = new SecurityElement ("string");
str.Text = SecurityElement.Escape (ss);
value.AddChild (str);
}
}
se.AddChild (value);
}
using (FileStream fs = File.Create (file)){
StreamWriter sw = new StreamWriter (fs);
sw.Write (se.ToString ());
sw.Flush ();
}
}
private void AssertNotMarkedForDeletion ()
{
if (IsMarkedForDeletion)
throw RegistryKey.CreateMarkedForDeletionException ();
}
static string user_store;
static string machine_store;
private static string UserStore {
get {
if (user_store == null)
user_store = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal),
".mono/registry");
return user_store;
}
}
private static string MachineStore {
get {
if (machine_store == null) {
machine_store = Environment.GetEnvironmentVariable ("MONO_REGISTRY_PATH");
if (machine_store == null) {
string s = Environment.GetMachineConfigPath ();
int p = s.IndexOf ("machine.config");
machine_store = Path.Combine (Path.Combine (s.Substring (0, p-1), ".."), "registry");
}
}
return machine_store;
}
}
}
internal class UnixRegistryApi : IRegistryApi {
static string ToUnix (string keyname)
{
if (keyname.IndexOf ('\\') != -1)
keyname = keyname.Replace ('\\', '/');
return keyname.ToLower ();
}
static bool IsWellKnownKey (string parentKeyName, string keyname)
{
// FIXME: Add more keys if needed
if (parentKeyName == Registry.CurrentUser.Name ||
parentKeyName == Registry.LocalMachine.Name)
return (0 == String.Compare ("software", keyname, true, CultureInfo.InvariantCulture));
return false;
}
public RegistryKey CreateSubKey (RegistryKey rkey, string keyname)
{
return CreateSubKey (rkey, keyname, true);
}
public RegistryKey CreateSubKey (RegistryKey rkey, string keyname, RegistryOptions options)
{
return CreateSubKey (rkey, keyname, true, options == RegistryOptions.Volatile);
}
public RegistryKey OpenRemoteBaseKey (RegistryHive hKey, string machineName)
{
throw new NotImplementedException ();
}
public RegistryKey OpenSubKey (RegistryKey rkey, string keyname, bool writable)
{
KeyHandler self = KeyHandler.Lookup (rkey, true);
if (self == null) {
// return null if parent is marked for deletion
return null;
}
RegistryKey result = self.Probe (rkey, ToUnix (keyname), writable);
if (result == null && IsWellKnownKey (rkey.Name, keyname)) {
// create the subkey even if its parent was opened read-only
result = CreateSubKey (rkey, keyname, writable);
}
return result;
}
public RegistryKey FromHandle (SafeRegistryHandle handle)
{
throw new NotImplementedException ();
}
public void Flush (RegistryKey rkey)
{
KeyHandler self = KeyHandler.Lookup (rkey, false);
if (self == null) {
// we do not need to flush changes as key is marked for deletion
return;
}
self.Flush ();
}
public void Close (RegistryKey rkey)
{
KeyHandler.Drop (rkey);
}
public object GetValue (RegistryKey rkey, string name, object default_value, RegistryValueOptions options)
{
KeyHandler self = KeyHandler.Lookup (rkey, true);
if (self == null) {
// key was removed since it was opened
return default_value;
}
if (self.ValueExists (name))
return self.GetValue (name, options);
return default_value;
}
public void SetValue (RegistryKey rkey, string name, object value)
{
KeyHandler self = KeyHandler.Lookup (rkey, true);
if (self == null)
throw RegistryKey.CreateMarkedForDeletionException ();
self.SetValue (name, value);
}
public void SetValue (RegistryKey rkey, string name, object value, RegistryValueKind valueKind)
{
KeyHandler self = KeyHandler.Lookup (rkey, true);
if (self == null)
throw RegistryKey.CreateMarkedForDeletionException ();
self.SetValue (name, value, valueKind);
}
public int SubKeyCount (RegistryKey rkey)
{
KeyHandler self = KeyHandler.Lookup (rkey, true);
if (self == null)
throw RegistryKey.CreateMarkedForDeletionException ();
return self.GetSubKeyCount ();
}
public int ValueCount (RegistryKey rkey)
{
KeyHandler self = KeyHandler.Lookup (rkey, true);
if (self == null)
throw RegistryKey.CreateMarkedForDeletionException ();
return self.ValueCount;
}
public void DeleteValue (RegistryKey rkey, string name, bool throw_if_missing)
{
KeyHandler self = KeyHandler.Lookup (rkey, true);
if (self == null) {
// if key is marked for deletion, report success regardless of
// throw_if_missing
return;
}
if (throw_if_missing && !self.ValueExists (name))
throw new ArgumentException ("the given value does not exist");
self.RemoveValue (name);
}
public void DeleteKey (RegistryKey rkey, string keyname, bool throw_if_missing)
{
KeyHandler self = KeyHandler.Lookup (rkey, true);
if (self == null) {
// key is marked for deletion
if (!throw_if_missing)
return;
throw new ArgumentException ("the given value does not exist");
}
string dir = Path.Combine (self.Dir, ToUnix (keyname));
if (!KeyHandler.Delete (dir) && throw_if_missing)
throw new ArgumentException ("the given value does not exist");
}
public string [] GetSubKeyNames (RegistryKey rkey)
{
KeyHandler self = KeyHandler.Lookup (rkey, true);
return self.GetSubKeyNames ();
}
public string [] GetValueNames (RegistryKey rkey)
{
KeyHandler self = KeyHandler.Lookup (rkey, true);
if (self == null)
throw RegistryKey.CreateMarkedForDeletionException ();
return self.GetValueNames ();
}
public string ToString (RegistryKey rkey)
{
return rkey.Name;
}
private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable)
{
return CreateSubKey (rkey, keyname, writable, false);
}
private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable, bool is_volatile)
{
KeyHandler self = KeyHandler.Lookup (rkey, true);
if (self == null)
throw RegistryKey.CreateMarkedForDeletionException ();
if (KeyHandler.VolatileKeyExists (self.Dir) && !is_volatile)
throw new IOException ("Cannot create a non volatile subkey under a volatile key.");
return self.Ensure (rkey, ToUnix (keyname), writable, is_volatile);
}
public RegistryValueKind GetValueKind (RegistryKey rkey, string name)
{
KeyHandler self = KeyHandler.Lookup (rkey, true);
if (self != null)
return self.GetValueKind (name);
// key was removed since it was opened or it does not exist.
return RegistryValueKind.Unknown;
}
public IntPtr GetHandle (RegistryKey key)
{
throw new NotImplementedException ();
}
}
}
#endif // NET_2_1