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.Collections.Generic;
45 using System.Globalization;
48 using System.Runtime.InteropServices;
49 using System.Reflection;
50 using System.Security;
51 using System.Threading;
52 using Microsoft.Win32.SafeHandles;
54 namespace Microsoft.Win32 {
59 public ExpandString (string s)
64 public override string ToString ()
69 public string Expand ()
71 StringBuilder sb = new StringBuilder ();
73 for (int i = 0; i < value.Length; i++){
74 if (value [i] == '%'){
76 for (; j < value.Length; j++){
77 if (value [j] == '%'){
78 string key = value.Substring (i + 1, j - i - 1);
80 sb.Append (Environment.GetEnvironmentVariable (key));
85 if (j == value.Length){
89 sb.Append (value [i]);
92 return sb.ToString ();
96 class RegistryKeyComparer : IEqualityComparer {
97 public new bool Equals(object x, object y)
99 return RegistryKey.IsEquals ((RegistryKey) x, (RegistryKey) y);
103 public int GetHashCode(object obj)
105 var n = ((RegistryKey) obj).Name;
108 return n.GetHashCode ();
114 static Hashtable key_to_handler = new Hashtable (new RegistryKeyComparer ());
115 static Hashtable dir_to_handler = new Hashtable (
116 new CaseInsensitiveHashCodeProvider (), new CaseInsensitiveComparer ());
117 const string VolatileDirectoryName = "volatile-keys";
120 string ActualDir; // Lets keep this one private.
121 public bool IsVolatile;
129 CleanVolatileKeys ();
132 KeyHandler (RegistryKey rkey, string basedir) : this (rkey, basedir, false)
136 KeyHandler (RegistryKey rkey, string basedir, bool is_volatile)
138 // Force ourselved to reuse the key, if any.
139 string volatile_basedir = GetVolatileDir (basedir);
140 string actual_basedir = basedir;
142 if (Directory.Exists (basedir))
144 else if (Directory.Exists (volatile_basedir)) {
145 actual_basedir = volatile_basedir;
147 } else if (is_volatile)
148 actual_basedir = volatile_basedir;
150 if (!Directory.Exists (actual_basedir)) {
152 Directory.CreateDirectory (actual_basedir);
153 } catch (UnauthorizedAccessException ex){
154 throw new SecurityException ("No access to the given key", ex);
157 Dir = basedir; // This is our identifier.
158 ActualDir = actual_basedir; // This our actual location.
159 IsVolatile = is_volatile;
160 file = Path.Combine (ActualDir, "values.xml");
166 values = new Hashtable ();
167 if (!File.Exists (file))
171 using (FileStream fs = File.OpenRead (file)){
172 StreamReader r = new StreamReader (fs);
173 string xml = r.ReadToEnd ();
177 SecurityElement tree = SecurityElement.FromString (xml);
178 if (tree.Tag == "values" && tree.Children != null){
179 foreach (SecurityElement value in tree.Children){
180 if (value.Tag == "value"){
186 } catch (UnauthorizedAccessException){
188 throw new SecurityException ("No access to the given key");
189 } catch (Exception e){
190 Console.Error.WriteLine ("While loading registry key at {0}: {1}", file, e);
195 void LoadKey (SecurityElement se)
197 Hashtable h = se.Attributes;
199 string name = (string) h ["name"];
202 string type = (string) h ["type"];
208 values [name] = Int32.Parse (se.Text);
211 values [name] = Convert.FromBase64String (se.Text);
214 values [name] = se.Text == null ? String.Empty : se.Text;
217 values [name] = new ExpandString (se.Text);
220 values [name] = Int64.Parse (se.Text);
223 var sa = new List<string> ();
224 if (se.Children != null){
225 foreach (SecurityElement stre in se.Children){
229 values [name] = sa.ToArray ();
233 // We ignore individual errors in the file.
237 public RegistryKey Ensure (RegistryKey rkey, string extra, bool writable)
239 return Ensure (rkey, extra, writable, false);
242 // 'is_volatile' is used only if the key hasn't been created already.
243 public RegistryKey Ensure (RegistryKey rkey, string extra, bool writable, bool is_volatile)
245 lock (typeof (KeyHandler)){
246 string f = Path.Combine (Dir, extra);
247 KeyHandler kh = (KeyHandler) dir_to_handler [f];
249 kh = new KeyHandler (rkey, f, is_volatile);
250 RegistryKey rk = new RegistryKey (kh, CombineName (rkey, extra), writable);
251 key_to_handler [rk] = kh;
252 dir_to_handler [f] = kh;
257 public RegistryKey Probe (RegistryKey rkey, string extra, bool writable)
259 RegistryKey rk = null;
261 lock (typeof (KeyHandler)){
262 string f = Path.Combine (Dir, extra);
263 KeyHandler kh = (KeyHandler) dir_to_handler [f];
265 rk = new RegistryKey (kh, CombineName (rkey,
267 key_to_handler [rk] = kh;
268 } else if (Directory.Exists (f) || VolatileKeyExists (f)) {
269 kh = new KeyHandler (rkey, f);
270 rk = new RegistryKey (kh, CombineName (rkey, extra),
272 dir_to_handler [f] = kh;
273 key_to_handler [rk] = kh;
279 static string CombineName (RegistryKey rkey, string extra)
281 if (extra.IndexOf ('/') != -1)
282 extra = extra.Replace ('/', '\\');
284 return String.Concat (rkey.Name, "\\", extra);
287 static long GetSystemBootTime ()
289 if (!File.Exists ("/proc/stat"))
296 using (StreamReader stat_file = new StreamReader ("/proc/stat", Encoding.ASCII)) {
297 while ((line = stat_file.ReadLine ()) != null)
298 if (line.StartsWith ("btime")) {
303 } catch (Exception e) {
304 Console.Error.WriteLine ("While reading system info {0}", e);
310 int space = btime.IndexOf (' ');
312 if (!Int64.TryParse (btime.Substring (space, btime.Length - space), out res))
318 // The registered boot time it's a simple line containing the last system btime.
319 static long GetRegisteredBootTime (string path)
321 if (!File.Exists (path))
326 using (StreamReader reader = new StreamReader (path, Encoding.ASCII))
327 line = reader.ReadLine ();
328 } catch (Exception e) {
329 Console.Error.WriteLine ("While reading registry data at {0}: {1}", path, e);
336 if (!Int64.TryParse (line, out res))
342 static void SaveRegisteredBootTime (string path, long btime)
345 using (StreamWriter writer = new StreamWriter (path, false, Encoding.ASCII))
346 writer.WriteLine (btime.ToString ());
347 } catch (Exception) {
348 /* This can happen when a user process tries to write to MachineStore */
349 //Console.Error.WriteLine ("While saving registry data at {0}: {1}", path, e);
353 // We save the last boot time in a last-btime file in every root, and we use it
354 // to clean the volatile keys directory in case the system btime changed.
355 static void CleanVolatileKeys ()
357 long system_btime = GetSystemBootTime ();
359 string [] roots = new string [] {
364 foreach (string root in roots) {
365 if (!Directory.Exists (root))
368 string btime_file = Path.Combine (root, "last-btime");
369 string volatile_dir = Path.Combine (root, VolatileDirectoryName);
371 if (Directory.Exists (volatile_dir)) {
372 long registered_btime = GetRegisteredBootTime (btime_file);
373 if (system_btime < 0 || registered_btime < 0 || registered_btime != system_btime)
374 Directory.Delete (volatile_dir, true);
377 SaveRegisteredBootTime (btime_file, system_btime);
381 public static bool VolatileKeyExists (string dir)
383 lock (typeof (KeyHandler)) {
384 KeyHandler kh = (KeyHandler) dir_to_handler [dir];
386 return kh.IsVolatile;
389 if (Directory.Exists (dir)) // Non-volatile key exists.
392 return Directory.Exists (GetVolatileDir (dir));
395 public static string GetVolatileDir (string dir)
397 string root = GetRootFromDir (dir);
398 string volatile_dir = dir.Replace (root, Path.Combine (root, VolatileDirectoryName));
402 public static KeyHandler Lookup (RegistryKey rkey, bool createNonExisting)
404 lock (typeof (KeyHandler)){
405 KeyHandler k = (KeyHandler) key_to_handler [rkey];
409 // when a non-root key is requested for no keyhandler exist
410 // then that key must have been marked for deletion
411 if (!rkey.IsRoot || !createNonExisting)
414 RegistryHive x = (RegistryHive) rkey.Hive;
416 case RegistryHive.CurrentUser:
417 string userDir = Path.Combine (UserStore, x.ToString ());
418 k = new KeyHandler (rkey, userDir);
419 dir_to_handler [userDir] = k;
421 case RegistryHive.CurrentConfig:
422 case RegistryHive.ClassesRoot:
423 case RegistryHive.DynData:
424 case RegistryHive.LocalMachine:
425 case RegistryHive.PerformanceData:
426 case RegistryHive.Users:
427 string machine_dir = Path.Combine (MachineStore, x.ToString ());
428 k = new KeyHandler (rkey, machine_dir);
429 dir_to_handler [machine_dir] = k;
432 throw new Exception ("Unknown RegistryHive");
434 key_to_handler [rkey] = k;
439 static string GetRootFromDir (string dir)
441 if (dir.IndexOf (UserStore) > -1)
443 else if (dir.IndexOf (MachineStore) > -1)
446 throw new Exception ("Could not get root for dir " + dir);
449 public static void Drop (RegistryKey rkey)
451 lock (typeof (KeyHandler)) {
452 KeyHandler k = (KeyHandler) key_to_handler [rkey];
455 key_to_handler.Remove (rkey);
457 // remove cached KeyHandler if no other keys reference it
459 foreach (DictionaryEntry de in key_to_handler)
463 dir_to_handler.Remove (k.Dir);
467 public static void Drop (string dir)
469 lock (typeof (KeyHandler)) {
470 KeyHandler kh = (KeyHandler) dir_to_handler [dir];
474 dir_to_handler.Remove (dir);
476 // remove (other) references to keyhandler
477 ArrayList keys = new ArrayList ();
478 foreach (DictionaryEntry de in key_to_handler)
482 foreach (object key in keys)
483 key_to_handler.Remove (key);
487 public static bool Delete (string dir)
489 if (!Directory.Exists (dir)) {
490 string volatile_dir = GetVolatileDir (dir);
491 if (!Directory.Exists (volatile_dir))
497 Directory.Delete (dir, true);
502 public RegistryValueKind GetValueKind (string name)
505 return RegistryValueKind.Unknown;
509 value = values [name];
512 return RegistryValueKind.Unknown;
515 return RegistryValueKind.DWord;
516 if (value is string [])
517 return RegistryValueKind.MultiString;
519 return RegistryValueKind.QWord;
520 if (value is byte [])
521 return RegistryValueKind.Binary;
523 return RegistryValueKind.String;
524 if (value is ExpandString)
525 return RegistryValueKind.ExpandString;
526 return RegistryValueKind.Unknown;
529 public object GetValue (string name, RegistryValueOptions options)
531 if (IsMarkedForDeletion)
538 value = values [name];
539 ExpandString exp = value as ExpandString;
542 if ((options & RegistryValueOptions.DoNotExpandEnvironmentNames) == 0)
543 return exp.Expand ();
545 return exp.ToString ();
548 public void SetValue (string name, object value)
550 AssertNotMarkedForDeletion ();
556 // immediately convert non-native registry values to string to avoid
557 // returning it unmodified in calls to UnixRegistryApi.GetValue
558 if (value is int || value is string || value is byte[] || value is string[])
559 values[name] = value;
561 values[name] = value.ToString ();
566 public string [] GetValueNames ()
568 AssertNotMarkedForDeletion ();
571 ICollection keys = values.Keys;
573 string [] vals = new string [keys.Count];
574 keys.CopyTo (vals, 0);
579 public int GetSubKeyCount ()
581 return GetSubKeyNames ().Length;
584 public string [] GetSubKeyNames ()
586 DirectoryInfo selfDir = new DirectoryInfo (ActualDir);
587 DirectoryInfo[] subDirs = selfDir.GetDirectories ();
588 string[] subKeyNames;
590 // for volatile keys (cannot contain non-volatile subkeys) or keys
591 // without *any* presence in the volatile key section, we can do it simple.
592 if (IsVolatile || !Directory.Exists (GetVolatileDir (Dir))) {
593 subKeyNames = new string[subDirs.Length];
594 for (int i = 0; i < subDirs.Length; i++) {
595 DirectoryInfo subDir = subDirs[i];
596 subKeyNames[i] = subDir.Name;
601 // We may have the entries repeated, so keep just one of each one.
602 DirectoryInfo volatileDir = new DirectoryInfo (GetVolatileDir (Dir));
603 DirectoryInfo [] volatileSubDirs = volatileDir.GetDirectories ();
604 Dictionary<string,string> dirs = new Dictionary<string,string> ();
606 foreach (DirectoryInfo dir in subDirs)
607 dirs [dir.Name] = dir.Name;
608 foreach (DirectoryInfo volDir in volatileSubDirs)
609 dirs [volDir.Name] = volDir.Name;
611 subKeyNames = new string [dirs.Count];
613 foreach (KeyValuePair<string,string> entry in dirs)
614 subKeyNames[j++] = entry.Value;
620 // This version has to do argument validation based on the valueKind
622 public void SetValue (string name, object value, RegistryValueKind valueKind)
631 case RegistryValueKind.String:
632 if (value is string){
633 values [name] = value;
637 case RegistryValueKind.ExpandString:
638 if (value is string){
639 values [name] = new ExpandString ((string)value);
644 case RegistryValueKind.Binary:
645 if (value is byte []){
646 values [name] = value;
651 case RegistryValueKind.DWord:
653 values [name] = Convert.ToInt32 (value);
655 } catch (OverflowException) {
659 case RegistryValueKind.MultiString:
660 if (value is string []){
661 values [name] = value;
666 case RegistryValueKind.QWord:
668 values [name] = Convert.ToInt64 (value);
670 } catch (OverflowException) {
675 throw new ArgumentException ("unknown value", "valueKind");
678 throw new ArgumentException ("Value could not be converted to specified type", "valueKind");
683 lock (typeof (KeyHandler)){
687 new Timer (DirtyTimeout, null, 3000, Timeout.Infinite);
691 public void DirtyTimeout (object state)
698 lock (typeof (KeyHandler)) {
706 public bool ValueExists (string name)
712 return values.Contains (name);
715 public int ValueCount {
718 return values.Keys.Count;
722 public bool IsMarkedForDeletion {
724 return !dir_to_handler.Contains (Dir);
728 public void RemoveValue (string name)
730 AssertNotMarkedForDeletion ();
733 values.Remove (name);
744 if (IsMarkedForDeletion)
747 SecurityElement se = new SecurityElement ("values");
750 if (!File.Exists (file) && values.Count == 0)
753 // With SecurityElement.Text = value, and SecurityElement.AddAttribute(key, value)
754 // the values must be escaped prior to being assigned.
755 foreach (DictionaryEntry de in values){
756 object val = de.Value;
757 SecurityElement value = new SecurityElement ("value");
758 value.AddAttribute ("name", SecurityElement.Escape ((string) de.Key));
761 value.AddAttribute ("type", "string");
762 value.Text = SecurityElement.Escape ((string) val);
763 } else if (val is int){
764 value.AddAttribute ("type", "int");
765 value.Text = val.ToString ();
766 } else if (val is long) {
767 value.AddAttribute ("type", "qword");
768 value.Text = val.ToString ();
769 } else if (val is byte []){
770 value.AddAttribute ("type", "bytearray");
771 value.Text = Convert.ToBase64String ((byte[]) val);
772 } else if (val is ExpandString){
773 value.AddAttribute ("type", "expand");
774 value.Text = SecurityElement.Escape (val.ToString ());
775 } else if (val is string []){
776 value.AddAttribute ("type", "string-array");
778 foreach (string ss in (string[]) val){
779 SecurityElement str = new SecurityElement ("string");
780 str.Text = SecurityElement.Escape (ss);
781 value.AddChild (str);
788 using (FileStream fs = File.Create (file)){
789 StreamWriter sw = new StreamWriter (fs);
791 sw.Write (se.ToString ());
796 private void AssertNotMarkedForDeletion ()
798 if (IsMarkedForDeletion)
799 throw RegistryKey.CreateMarkedForDeletionException ();
802 static string user_store;
803 static string machine_store;
805 private static string UserStore {
807 if (user_store == null)
808 user_store = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal),
815 private static string MachineStore {
817 if (machine_store == null) {
818 machine_store = Environment.GetEnvironmentVariable ("MONO_REGISTRY_PATH");
819 if (machine_store == null) {
820 string s = Environment.GetMachineConfigPath ();
821 int p = s.IndexOf ("machine.config");
822 machine_store = Path.Combine (Path.Combine (s.Substring (0, p-1), ".."), "registry");
826 return machine_store;
831 internal class UnixRegistryApi : IRegistryApi {
833 static string ToUnix (string keyname)
835 if (keyname.IndexOf ('\\') != -1)
836 keyname = keyname.Replace ('\\', '/');
837 return keyname.ToLower ();
840 static bool IsWellKnownKey (string parentKeyName, string keyname)
842 // FIXME: Add more keys if needed
843 if (parentKeyName == Registry.CurrentUser.Name ||
844 parentKeyName == Registry.LocalMachine.Name)
845 return (0 == String.Compare ("software", keyname, true, CultureInfo.InvariantCulture));
850 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname)
852 return CreateSubKey (rkey, keyname, true);
855 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname, RegistryOptions options)
857 return CreateSubKey (rkey, keyname, true, options == RegistryOptions.Volatile);
860 public RegistryKey OpenRemoteBaseKey (RegistryHive hKey, string machineName)
862 throw new NotImplementedException ();
865 public RegistryKey OpenSubKey (RegistryKey rkey, string keyname, bool writable)
867 KeyHandler self = KeyHandler.Lookup (rkey, true);
869 // return null if parent is marked for deletion
873 RegistryKey result = self.Probe (rkey, ToUnix (keyname), writable);
874 if (result == null && IsWellKnownKey (rkey.Name, keyname)) {
875 // create the subkey even if its parent was opened read-only
876 result = CreateSubKey (rkey, keyname, writable);
882 public RegistryKey FromHandle (SafeRegistryHandle handle)
884 throw new NotImplementedException ();
887 public void Flush (RegistryKey rkey)
889 KeyHandler self = KeyHandler.Lookup (rkey, false);
891 // we do not need to flush changes as key is marked for deletion
897 public void Close (RegistryKey rkey)
899 KeyHandler.Drop (rkey);
902 public object GetValue (RegistryKey rkey, string name, object default_value, RegistryValueOptions options)
904 KeyHandler self = KeyHandler.Lookup (rkey, true);
906 // key was removed since it was opened
907 return default_value;
910 if (self.ValueExists (name))
911 return self.GetValue (name, options);
912 return default_value;
915 public void SetValue (RegistryKey rkey, string name, object value)
917 KeyHandler self = KeyHandler.Lookup (rkey, true);
919 throw RegistryKey.CreateMarkedForDeletionException ();
920 self.SetValue (name, value);
923 public void SetValue (RegistryKey rkey, string name, object value, RegistryValueKind valueKind)
925 KeyHandler self = KeyHandler.Lookup (rkey, true);
927 throw RegistryKey.CreateMarkedForDeletionException ();
928 self.SetValue (name, value, valueKind);
931 public int SubKeyCount (RegistryKey rkey)
933 KeyHandler self = KeyHandler.Lookup (rkey, true);
935 throw RegistryKey.CreateMarkedForDeletionException ();
936 return self.GetSubKeyCount ();
939 public int ValueCount (RegistryKey rkey)
941 KeyHandler self = KeyHandler.Lookup (rkey, true);
943 throw RegistryKey.CreateMarkedForDeletionException ();
944 return self.ValueCount;
947 public void DeleteValue (RegistryKey rkey, string name, bool throw_if_missing)
949 KeyHandler self = KeyHandler.Lookup (rkey, true);
951 // if key is marked for deletion, report success regardless of
956 if (throw_if_missing && !self.ValueExists (name))
957 throw new ArgumentException ("the given value does not exist");
959 self.RemoveValue (name);
962 public void DeleteKey (RegistryKey rkey, string keyname, bool throw_if_missing)
964 KeyHandler self = KeyHandler.Lookup (rkey, true);
966 // key is marked for deletion
967 if (!throw_if_missing)
969 throw new ArgumentException ("the given value does not exist");
972 string dir = Path.Combine (self.Dir, ToUnix (keyname));
974 if (!KeyHandler.Delete (dir) && throw_if_missing)
975 throw new ArgumentException ("the given value does not exist");
978 public string [] GetSubKeyNames (RegistryKey rkey)
980 KeyHandler self = KeyHandler.Lookup (rkey, true);
981 return self.GetSubKeyNames ();
984 public string [] GetValueNames (RegistryKey rkey)
986 KeyHandler self = KeyHandler.Lookup (rkey, true);
988 throw RegistryKey.CreateMarkedForDeletionException ();
989 return self.GetValueNames ();
992 public string ToString (RegistryKey rkey)
997 private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable)
999 return CreateSubKey (rkey, keyname, writable, false);
1002 private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable, bool is_volatile)
1004 KeyHandler self = KeyHandler.Lookup (rkey, true);
1006 throw RegistryKey.CreateMarkedForDeletionException ();
1008 if (KeyHandler.VolatileKeyExists (self.Dir) && !is_volatile)
1009 throw new IOException ("Cannot create a non volatile subkey under a volatile key.");
1011 return self.Ensure (rkey, ToUnix (keyname), writable, is_volatile);
1014 public RegistryValueKind GetValueKind (RegistryKey rkey, string name)
1016 KeyHandler self = KeyHandler.Lookup (rkey, true);
1018 return self.GetValueKind (name);
1020 // key was removed since it was opened or it does not exist.
1021 return RegistryValueKind.Unknown;
1024 public IntPtr GetHandle (RegistryKey key)
1026 throw new NotImplementedException ();