2 // Microsoft.Win32/UnixRegistryApi.cs
5 // Miguel de Icaza (miguel@gnome.org)
6 // Gert Driesen (drieseng@users.sourceforge.net)
8 // (C) 2005, 2006 Novell, Inc (http://www.novell.com)
11 // It would be useful if we do case-insensitive expansion of variables,
12 // the registry is very windows specific, so we probably should default to
13 // those semantics in expanding environment variables, for example %path%
15 // We should use an ordered collection for storing the values (instead of
18 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
20 // Permission is hereby granted, free of charge, to any person obtaining
21 // a copy of this software and associated documentation files (the
22 // "Software"), to deal in the Software without restriction, including
23 // without limitation the rights to use, copy, modify, merge, publish,
24 // distribute, sublicense, and/or sell copies of the Software, and to
25 // permit persons to whom the Software is furnished to do so, subject to
26 // the following conditions:
28 // The above copyright notice and this permission notice shall be
29 // included in all copies or substantial portions of the Software.
31 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
32 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
33 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
34 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
35 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
36 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
37 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
43 using System.Collections;
44 using System.Globalization;
47 using System.Runtime.InteropServices;
48 using System.Reflection;
49 using System.Security;
50 using System.Threading;
52 namespace Microsoft.Win32 {
57 public ExpandString (string s)
62 public override string ToString ()
67 public string Expand ()
69 StringBuilder sb = new StringBuilder ();
71 for (int i = 0; i < value.Length; i++){
72 if (value [i] == '%'){
74 for (; j < value.Length; j++){
75 if (value [j] == '%'){
76 string key = value.Substring (i + 1, j - i - 1);
78 sb.Append (Environment.GetEnvironmentVariable (key));
83 if (j == value.Length){
87 sb.Append (value [i]);
90 return sb.ToString ();
96 static Hashtable key_to_handler = new Hashtable ();
97 static Hashtable dir_to_handler = new Hashtable (
98 new CaseInsensitiveHashCodeProvider (), new CaseInsensitiveComparer ());
99 const string VolatileDirectoryName = "volatile-keys";
102 string ActualDir; // Lets keep this one private.
103 public bool IsVolatile;
109 KeyHandler (RegistryKey rkey, string basedir) : this (rkey, basedir, false)
113 KeyHandler (RegistryKey rkey, string basedir, bool is_volatile)
115 // Force ourselved to reuse the key, if any.
116 string volatile_basedir = GetVolatileDir (basedir);
117 string actual_basedir = basedir;
119 if (Directory.Exists (basedir))
121 else if (Directory.Exists (volatile_basedir)) {
122 actual_basedir = volatile_basedir;
124 } else if (is_volatile)
125 actual_basedir = volatile_basedir;
127 if (!Directory.Exists (actual_basedir)) {
129 Directory.CreateDirectory (actual_basedir);
130 } catch (UnauthorizedAccessException){
131 throw new SecurityException ("No access to the given key");
134 Dir = basedir; // This is our identifier.
135 ActualDir = actual_basedir; // This our actual location.
136 IsVolatile = is_volatile;
137 file = Path.Combine (ActualDir, "values.xml");
143 values = new Hashtable ();
144 if (!File.Exists (file))
148 using (FileStream fs = File.OpenRead (file)){
149 StreamReader r = new StreamReader (fs);
150 string xml = r.ReadToEnd ();
154 SecurityElement tree = SecurityElement.FromString (xml);
155 if (tree.Tag == "values" && tree.Children != null){
156 foreach (SecurityElement value in tree.Children){
157 if (value.Tag == "value"){
163 } catch (UnauthorizedAccessException){
165 throw new SecurityException ("No access to the given key");
166 } catch (Exception e){
167 Console.Error.WriteLine ("While loading registry key at {0}: {1}", file, e);
172 void LoadKey (SecurityElement se)
174 Hashtable h = se.Attributes;
176 string name = (string) h ["name"];
179 string type = (string) h ["type"];
185 values [name] = Int32.Parse (se.Text);
188 values [name] = Convert.FromBase64String (se.Text);
191 values [name] = se.Text == null ? String.Empty : se.Text;
194 values [name] = new ExpandString (se.Text);
197 values [name] = Int64.Parse (se.Text);
200 ArrayList sa = new ArrayList ();
201 if (se.Children != null){
202 foreach (SecurityElement stre in se.Children){
206 values [name] = sa.ToArray (typeof (string));
210 // We ignore individual errors in the file.
214 public RegistryKey Ensure (RegistryKey rkey, string extra, bool writable)
216 return Ensure (rkey, extra, writable, false);
219 // 'is_volatile' is used only if the key hasn't been created already.
220 public RegistryKey Ensure (RegistryKey rkey, string extra, bool writable, bool is_volatile)
222 lock (typeof (KeyHandler)){
223 string f = Path.Combine (Dir, extra);
224 KeyHandler kh = (KeyHandler) dir_to_handler [f];
226 kh = new KeyHandler (rkey, f, is_volatile);
227 RegistryKey rk = new RegistryKey (kh, CombineName (rkey, extra), writable);
228 key_to_handler [rk] = kh;
229 dir_to_handler [f] = kh;
234 public RegistryKey Probe (RegistryKey rkey, string extra, bool writable)
236 RegistryKey rk = null;
238 lock (typeof (KeyHandler)){
239 string f = Path.Combine (Dir, extra);
240 KeyHandler kh = (KeyHandler) dir_to_handler [f];
242 rk = new RegistryKey (kh, CombineName (rkey,
244 key_to_handler [rk] = kh;
245 } else if (Directory.Exists (f) || VolatileKeyExists (f)) {
246 kh = new KeyHandler (rkey, f);
247 rk = new RegistryKey (kh, CombineName (rkey, extra),
249 dir_to_handler [f] = kh;
250 key_to_handler [rk] = kh;
256 static string CombineName (RegistryKey rkey, string extra)
258 if (extra.IndexOf ('/') != -1)
259 extra = extra.Replace ('/', '\\');
261 return String.Concat (rkey.Name, "\\", extra);
264 public static bool VolatileKeyExists (string dir)
266 lock (typeof (KeyHandler)) {
267 KeyHandler kh = (KeyHandler) dir_to_handler [dir];
269 return kh.IsVolatile;
272 if (Directory.Exists (dir)) // Non-volatile key exists.
275 return Directory.Exists (GetVolatileDir (dir));
278 public static string GetVolatileDir (string dir)
280 string root = GetRootFromDir (dir);
281 string volatile_dir = dir.Replace (root, Path.Combine (root, VolatileDirectoryName));
285 public static KeyHandler Lookup (RegistryKey rkey, bool createNonExisting)
287 lock (typeof (KeyHandler)){
288 KeyHandler k = (KeyHandler) key_to_handler [rkey];
292 // when a non-root key is requested for no keyhandler exist
293 // then that key must have been marked for deletion
294 if (!rkey.IsRoot || !createNonExisting)
297 RegistryHive x = (RegistryHive) rkey.Hive;
299 case RegistryHive.CurrentUser:
300 string userDir = Path.Combine (UserStore, x.ToString ());
301 k = new KeyHandler (rkey, userDir);
302 dir_to_handler [userDir] = k;
304 case RegistryHive.CurrentConfig:
305 case RegistryHive.ClassesRoot:
306 case RegistryHive.DynData:
307 case RegistryHive.LocalMachine:
308 case RegistryHive.PerformanceData:
309 case RegistryHive.Users:
310 string machine_dir = Path.Combine (MachineStore, x.ToString ());
311 k = new KeyHandler (rkey, machine_dir);
312 dir_to_handler [machine_dir] = k;
315 throw new Exception ("Unknown RegistryHive");
317 key_to_handler [rkey] = k;
322 static string GetRootFromDir (string dir)
324 if (dir.IndexOf (UserStore) > -1)
326 else if (dir.IndexOf (MachineStore) > -1)
329 throw new Exception ("Could not get root for dir " + dir);
332 public static void Drop (RegistryKey rkey)
334 lock (typeof (KeyHandler)) {
335 KeyHandler k = (KeyHandler) key_to_handler [rkey];
338 key_to_handler.Remove (rkey);
340 // remove cached KeyHandler if no other keys reference it
342 foreach (DictionaryEntry de in key_to_handler)
346 dir_to_handler.Remove (k.Dir);
350 public static void Drop (string dir)
352 lock (typeof (KeyHandler)) {
353 KeyHandler kh = (KeyHandler) dir_to_handler [dir];
357 dir_to_handler.Remove (dir);
359 // remove (other) references to keyhandler
360 ArrayList keys = new ArrayList ();
361 foreach (DictionaryEntry de in key_to_handler)
365 foreach (object key in keys)
366 key_to_handler.Remove (key);
370 public static bool Delete (string dir)
372 if (!Directory.Exists (dir)) {
373 string volatile_dir = GetVolatileDir (dir);
374 if (!Directory.Exists (volatile_dir))
380 Directory.Delete (dir, true);
385 public RegistryValueKind GetValueKind (string name)
388 return RegistryValueKind.Unknown;
389 object value = values [name];
391 return RegistryValueKind.Unknown;
394 return RegistryValueKind.DWord;
395 if (value is string [])
396 return RegistryValueKind.MultiString;
398 return RegistryValueKind.QWord;
399 if (value is byte [])
400 return RegistryValueKind.Binary;
402 return RegistryValueKind.String;
403 if (value is ExpandString)
404 return RegistryValueKind.ExpandString;
405 return RegistryValueKind.Unknown;
408 public object GetValue (string name, RegistryValueOptions options)
410 if (IsMarkedForDeletion)
415 object value = values [name];
416 ExpandString exp = value as ExpandString;
419 if ((options & RegistryValueOptions.DoNotExpandEnvironmentNames) == 0)
420 return exp.Expand ();
422 return exp.ToString ();
425 public void SetValue (string name, object value)
427 AssertNotMarkedForDeletion ();
432 // immediately convert non-native registry values to string to avoid
433 // returning it unmodified in calls to UnixRegistryApi.GetValue
434 if (value is int || value is string || value is byte[] || value is string[])
435 values[name] = value;
437 values[name] = value.ToString ();
441 public string [] GetValueNames ()
443 AssertNotMarkedForDeletion ();
445 ICollection keys = values.Keys;
447 string [] vals = new string [keys.Count];
448 keys.CopyTo (vals, 0);
453 // This version has to do argument validation based on the valueKind
455 public void SetValue (string name, object value, RegistryValueKind valueKind)
463 case RegistryValueKind.String:
464 if (value is string){
465 values [name] = value;
469 case RegistryValueKind.ExpandString:
470 if (value is string){
471 values [name] = new ExpandString ((string)value);
476 case RegistryValueKind.Binary:
477 if (value is byte []){
478 values [name] = value;
483 case RegistryValueKind.DWord:
485 (((long) value) < Int32.MaxValue) &&
486 (((long) value) > Int32.MinValue)){
487 values [name] = (int) ((long)value);
491 values [name] = value;
496 case RegistryValueKind.MultiString:
497 if (value is string []){
498 values [name] = value;
503 case RegistryValueKind.QWord:
505 values [name] = (long) ((int) value);
509 values [name] = value;
514 throw new ArgumentException ("unknown value", "valueKind");
516 throw new ArgumentException ("Value could not be converted to specified type", "valueKind");
521 lock (typeof (KeyHandler)){
525 new Timer (DirtyTimeout, null, 3000, Timeout.Infinite);
529 public void DirtyTimeout (object state)
536 lock (typeof (KeyHandler)) {
544 public bool ValueExists (string name)
549 return values.Contains (name);
552 public int ValueCount {
554 return values.Keys.Count;
558 public bool IsMarkedForDeletion {
560 return !dir_to_handler.Contains (Dir);
564 public void RemoveValue (string name)
566 AssertNotMarkedForDeletion ();
568 values.Remove (name);
579 if (IsMarkedForDeletion)
582 if (!File.Exists (file) && values.Count == 0)
585 SecurityElement se = new SecurityElement ("values");
587 // With SecurityElement.Text = value, and SecurityElement.AddAttribute(key, value)
588 // the values must be escaped prior to being assigned.
589 foreach (DictionaryEntry de in values){
590 object val = de.Value;
591 SecurityElement value = new SecurityElement ("value");
592 value.AddAttribute ("name", SecurityElement.Escape ((string) de.Key));
595 value.AddAttribute ("type", "string");
596 value.Text = SecurityElement.Escape ((string) val);
597 } else if (val is int){
598 value.AddAttribute ("type", "int");
599 value.Text = val.ToString ();
600 } else if (val is long) {
601 value.AddAttribute ("type", "qword");
602 value.Text = val.ToString ();
603 } else if (val is byte []){
604 value.AddAttribute ("type", "bytearray");
605 value.Text = Convert.ToBase64String ((byte[]) val);
606 } else if (val is ExpandString){
607 value.AddAttribute ("type", "expand");
608 value.Text = SecurityElement.Escape (val.ToString ());
609 } else if (val is string []){
610 value.AddAttribute ("type", "string-array");
612 foreach (string ss in (string[]) val){
613 SecurityElement str = new SecurityElement ("string");
614 str.Text = SecurityElement.Escape (ss);
615 value.AddChild (str);
621 using (FileStream fs = File.Create (file)){
622 StreamWriter sw = new StreamWriter (fs);
624 sw.Write (se.ToString ());
629 private void AssertNotMarkedForDeletion ()
631 if (IsMarkedForDeletion)
632 throw RegistryKey.CreateMarkedForDeletionException ();
635 static string user_store;
636 static string machine_store;
638 private static string UserStore {
640 if (user_store == null)
641 user_store = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal),
648 private static string MachineStore {
650 if (machine_store == null) {
651 machine_store = Environment.GetEnvironmentVariable ("MONO_REGISTRY_PATH");
652 if (machine_store == null) {
653 string s = Environment.GetMachineConfigPath ();
654 int p = s.IndexOf ("machine.config");
655 machine_store = Path.Combine (Path.Combine (s.Substring (0, p-1), ".."), "registry");
659 return machine_store;
664 internal class UnixRegistryApi : IRegistryApi {
666 static string ToUnix (string keyname)
668 if (keyname.IndexOf ('\\') != -1)
669 keyname = keyname.Replace ('\\', '/');
670 return keyname.ToLower ();
673 static bool IsWellKnownKey (string parentKeyName, string keyname)
675 // FIXME: Add more keys if needed
676 if (parentKeyName == Registry.CurrentUser.Name ||
677 parentKeyName == Registry.LocalMachine.Name)
678 return (0 == String.Compare ("software", keyname, true, CultureInfo.InvariantCulture));
683 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname)
685 return CreateSubKey (rkey, keyname, true);
689 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname, RegistryOptions options)
691 return CreateSubKey (rkey, keyname, true, options == RegistryOptions.Volatile);
695 public RegistryKey OpenRemoteBaseKey (RegistryHive hKey, string machineName)
697 throw new NotImplementedException ();
700 public RegistryKey OpenSubKey (RegistryKey rkey, string keyname, bool writable)
702 KeyHandler self = KeyHandler.Lookup (rkey, true);
704 // return null if parent is marked for deletion
708 RegistryKey result = self.Probe (rkey, ToUnix (keyname), writable);
709 if (result == null && IsWellKnownKey (rkey.Name, keyname)) {
710 // create the subkey even if its parent was opened read-only
711 result = CreateSubKey (rkey, keyname, writable);
717 public void Flush (RegistryKey rkey)
719 KeyHandler self = KeyHandler.Lookup (rkey, false);
721 // we do not need to flush changes as key is marked for deletion
727 public void Close (RegistryKey rkey)
729 KeyHandler.Drop (rkey);
732 public object GetValue (RegistryKey rkey, string name, object default_value, RegistryValueOptions options)
734 KeyHandler self = KeyHandler.Lookup (rkey, true);
736 // key was removed since it was opened
737 return default_value;
740 if (self.ValueExists (name))
741 return self.GetValue (name, options);
742 return default_value;
745 public void SetValue (RegistryKey rkey, string name, object value)
747 KeyHandler self = KeyHandler.Lookup (rkey, true);
749 throw RegistryKey.CreateMarkedForDeletionException ();
750 self.SetValue (name, value);
753 public void SetValue (RegistryKey rkey, string name, object value, RegistryValueKind valueKind)
755 KeyHandler self = KeyHandler.Lookup (rkey, true);
757 throw RegistryKey.CreateMarkedForDeletionException ();
758 self.SetValue (name, value, valueKind);
761 public int SubKeyCount (RegistryKey rkey)
763 KeyHandler self = KeyHandler.Lookup (rkey, true);
765 throw RegistryKey.CreateMarkedForDeletionException ();
766 return Directory.GetDirectories (self.Dir).Length;
769 public int ValueCount (RegistryKey rkey)
771 KeyHandler self = KeyHandler.Lookup (rkey, true);
773 throw RegistryKey.CreateMarkedForDeletionException ();
774 return self.ValueCount;
777 public void DeleteValue (RegistryKey rkey, string name, bool throw_if_missing)
779 KeyHandler self = KeyHandler.Lookup (rkey, true);
781 // if key is marked for deletion, report success regardless of
786 if (throw_if_missing && !self.ValueExists (name))
787 throw new ArgumentException ("the given value does not exist");
789 self.RemoveValue (name);
792 public void DeleteKey (RegistryKey rkey, string keyname, bool throw_if_missing)
794 KeyHandler self = KeyHandler.Lookup (rkey, true);
796 // key is marked for deletion
797 if (!throw_if_missing)
799 throw new ArgumentException ("the given value does not exist");
802 string dir = Path.Combine (self.Dir, ToUnix (keyname));
804 if (!KeyHandler.Delete (dir) && throw_if_missing)
805 throw new ArgumentException ("the given value does not exist");
808 public string [] GetSubKeyNames (RegistryKey rkey)
810 KeyHandler self = KeyHandler.Lookup (rkey, true);
811 DirectoryInfo selfDir = new DirectoryInfo (self.Dir);
812 DirectoryInfo[] subDirs = selfDir.GetDirectories ();
813 string[] subKeyNames = new string[subDirs.Length];
814 for (int i = 0; i < subDirs.Length; i++) {
815 DirectoryInfo subDir = subDirs[i];
816 subKeyNames[i] = subDir.Name;
821 public string [] GetValueNames (RegistryKey rkey)
823 KeyHandler self = KeyHandler.Lookup (rkey, true);
825 throw RegistryKey.CreateMarkedForDeletionException ();
826 return self.GetValueNames ();
829 public string ToString (RegistryKey rkey)
834 private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable)
836 return CreateSubKey (rkey, keyname, writable, false);
839 private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable, bool is_volatile)
841 KeyHandler self = KeyHandler.Lookup (rkey, true);
843 throw RegistryKey.CreateMarkedForDeletionException ();
844 if (KeyHandler.VolatileKeyExists (self.Dir) && !is_volatile)
845 throw new IOException ("Cannot create a non volatile subkey under a volatile key.");
847 return self.Ensure (rkey, ToUnix (keyname), writable, is_volatile);
850 public RegistryValueKind GetValueKind (RegistryKey rkey, string name)
852 KeyHandler self = KeyHandler.Lookup (rkey, true);
854 return self.GetValueKind (name);
856 // key was removed since it was opened or it does not exist.
857 return RegistryValueKind.Unknown;