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 ();
98 static Hashtable key_to_handler = new Hashtable ();
99 static Hashtable dir_to_handler = new Hashtable (
100 new CaseInsensitiveHashCodeProvider (), new CaseInsensitiveComparer ());
101 const string VolatileDirectoryName = "volatile-keys";
104 string ActualDir; // Lets keep this one private.
105 public bool IsVolatile;
113 CleanVolatileKeys ();
116 KeyHandler (RegistryKey rkey, string basedir) : this (rkey, basedir, false)
120 KeyHandler (RegistryKey rkey, string basedir, bool is_volatile)
122 // Force ourselved to reuse the key, if any.
123 string volatile_basedir = GetVolatileDir (basedir);
124 string actual_basedir = basedir;
126 if (Directory.Exists (basedir))
128 else if (Directory.Exists (volatile_basedir)) {
129 actual_basedir = volatile_basedir;
131 } else if (is_volatile)
132 actual_basedir = volatile_basedir;
134 if (!Directory.Exists (actual_basedir)) {
136 Directory.CreateDirectory (actual_basedir);
137 } catch (UnauthorizedAccessException ex){
138 throw new SecurityException ("No access to the given key", ex);
141 Dir = basedir; // This is our identifier.
142 ActualDir = actual_basedir; // This our actual location.
143 IsVolatile = is_volatile;
144 file = Path.Combine (ActualDir, "values.xml");
150 values = new Hashtable ();
151 if (!File.Exists (file))
155 using (FileStream fs = File.OpenRead (file)){
156 StreamReader r = new StreamReader (fs);
157 string xml = r.ReadToEnd ();
161 SecurityElement tree = SecurityElement.FromString (xml);
162 if (tree.Tag == "values" && tree.Children != null){
163 foreach (SecurityElement value in tree.Children){
164 if (value.Tag == "value"){
170 } catch (UnauthorizedAccessException){
172 throw new SecurityException ("No access to the given key");
173 } catch (Exception e){
174 Console.Error.WriteLine ("While loading registry key at {0}: {1}", file, e);
179 void LoadKey (SecurityElement se)
181 Hashtable h = se.Attributes;
183 string name = (string) h ["name"];
186 string type = (string) h ["type"];
192 values [name] = Int32.Parse (se.Text);
195 values [name] = Convert.FromBase64String (se.Text);
198 values [name] = se.Text == null ? String.Empty : se.Text;
201 values [name] = new ExpandString (se.Text);
204 values [name] = Int64.Parse (se.Text);
207 var sa = new List<string> ();
208 if (se.Children != null){
209 foreach (SecurityElement stre in se.Children){
213 values [name] = sa.ToArray ();
217 // We ignore individual errors in the file.
221 public RegistryKey Ensure (RegistryKey rkey, string extra, bool writable)
223 return Ensure (rkey, extra, writable, false);
226 // 'is_volatile' is used only if the key hasn't been created already.
227 public RegistryKey Ensure (RegistryKey rkey, string extra, bool writable, bool is_volatile)
229 lock (typeof (KeyHandler)){
230 string f = Path.Combine (Dir, extra);
231 KeyHandler kh = (KeyHandler) dir_to_handler [f];
233 kh = new KeyHandler (rkey, f, is_volatile);
234 RegistryKey rk = new RegistryKey (kh, CombineName (rkey, extra), writable);
235 key_to_handler [rk] = kh;
236 dir_to_handler [f] = kh;
241 public RegistryKey Probe (RegistryKey rkey, string extra, bool writable)
243 RegistryKey rk = null;
245 lock (typeof (KeyHandler)){
246 string f = Path.Combine (Dir, extra);
247 KeyHandler kh = (KeyHandler) dir_to_handler [f];
249 rk = new RegistryKey (kh, CombineName (rkey,
251 key_to_handler [rk] = kh;
252 } else if (Directory.Exists (f) || VolatileKeyExists (f)) {
253 kh = new KeyHandler (rkey, f);
254 rk = new RegistryKey (kh, CombineName (rkey, extra),
256 dir_to_handler [f] = kh;
257 key_to_handler [rk] = kh;
263 static string CombineName (RegistryKey rkey, string extra)
265 if (extra.IndexOf ('/') != -1)
266 extra = extra.Replace ('/', '\\');
268 return String.Concat (rkey.Name, "\\", extra);
271 static long GetSystemBootTime ()
273 if (!File.Exists ("/proc/stat"))
280 using (StreamReader stat_file = new StreamReader ("/proc/stat", Encoding.ASCII)) {
281 while ((line = stat_file.ReadLine ()) != null)
282 if (line.StartsWith ("btime")) {
287 } catch (Exception e) {
288 Console.Error.WriteLine ("While reading system info {0}", e);
294 int space = btime.IndexOf (' ');
296 if (!Int64.TryParse (btime.Substring (space, btime.Length - space), out res))
302 // The registered boot time it's a simple line containing the last system btime.
303 static long GetRegisteredBootTime (string path)
305 if (!File.Exists (path))
310 using (StreamReader reader = new StreamReader (path, Encoding.ASCII))
311 line = reader.ReadLine ();
312 } catch (Exception e) {
313 Console.Error.WriteLine ("While reading registry data at {0}: {1}", path, e);
320 if (!Int64.TryParse (line, out res))
326 static void SaveRegisteredBootTime (string path, long btime)
329 using (StreamWriter writer = new StreamWriter (path, false, Encoding.ASCII))
330 writer.WriteLine (btime.ToString ());
331 } catch (Exception) {
332 /* This can happen when a user process tries to write to MachineStore */
333 //Console.Error.WriteLine ("While saving registry data at {0}: {1}", path, e);
337 // We save the last boot time in a last-btime file in every root, and we use it
338 // to clean the volatile keys directory in case the system btime changed.
339 static void CleanVolatileKeys ()
341 long system_btime = GetSystemBootTime ();
343 string [] roots = new string [] {
348 foreach (string root in roots) {
349 if (!Directory.Exists (root))
352 string btime_file = Path.Combine (root, "last-btime");
353 string volatile_dir = Path.Combine (root, VolatileDirectoryName);
355 if (Directory.Exists (volatile_dir)) {
356 long registered_btime = GetRegisteredBootTime (btime_file);
357 if (system_btime < 0 || registered_btime < 0 || registered_btime != system_btime)
358 Directory.Delete (volatile_dir, true);
361 SaveRegisteredBootTime (btime_file, system_btime);
365 public static bool VolatileKeyExists (string dir)
367 lock (typeof (KeyHandler)) {
368 KeyHandler kh = (KeyHandler) dir_to_handler [dir];
370 return kh.IsVolatile;
373 if (Directory.Exists (dir)) // Non-volatile key exists.
376 return Directory.Exists (GetVolatileDir (dir));
379 public static string GetVolatileDir (string dir)
381 string root = GetRootFromDir (dir);
382 string volatile_dir = dir.Replace (root, Path.Combine (root, VolatileDirectoryName));
386 public static KeyHandler Lookup (RegistryKey rkey, bool createNonExisting)
388 lock (typeof (KeyHandler)){
389 KeyHandler k = (KeyHandler) key_to_handler [rkey];
393 // when a non-root key is requested for no keyhandler exist
394 // then that key must have been marked for deletion
395 if (!rkey.IsRoot || !createNonExisting)
398 RegistryHive x = (RegistryHive) rkey.Hive;
400 case RegistryHive.CurrentUser:
401 string userDir = Path.Combine (UserStore, x.ToString ());
402 k = new KeyHandler (rkey, userDir);
403 dir_to_handler [userDir] = k;
405 case RegistryHive.CurrentConfig:
406 case RegistryHive.ClassesRoot:
407 case RegistryHive.DynData:
408 case RegistryHive.LocalMachine:
409 case RegistryHive.PerformanceData:
410 case RegistryHive.Users:
411 string machine_dir = Path.Combine (MachineStore, x.ToString ());
412 k = new KeyHandler (rkey, machine_dir);
413 dir_to_handler [machine_dir] = k;
416 throw new Exception ("Unknown RegistryHive");
418 key_to_handler [rkey] = k;
423 static string GetRootFromDir (string dir)
425 if (dir.IndexOf (UserStore) > -1)
427 else if (dir.IndexOf (MachineStore) > -1)
430 throw new Exception ("Could not get root for dir " + dir);
433 public static void Drop (RegistryKey rkey)
435 lock (typeof (KeyHandler)) {
436 KeyHandler k = (KeyHandler) key_to_handler [rkey];
439 key_to_handler.Remove (rkey);
441 // remove cached KeyHandler if no other keys reference it
443 foreach (DictionaryEntry de in key_to_handler)
447 dir_to_handler.Remove (k.Dir);
451 public static void Drop (string dir)
453 lock (typeof (KeyHandler)) {
454 KeyHandler kh = (KeyHandler) dir_to_handler [dir];
458 dir_to_handler.Remove (dir);
460 // remove (other) references to keyhandler
461 ArrayList keys = new ArrayList ();
462 foreach (DictionaryEntry de in key_to_handler)
466 foreach (object key in keys)
467 key_to_handler.Remove (key);
471 public static bool Delete (string dir)
473 if (!Directory.Exists (dir)) {
474 string volatile_dir = GetVolatileDir (dir);
475 if (!Directory.Exists (volatile_dir))
481 Directory.Delete (dir, true);
486 public RegistryValueKind GetValueKind (string name)
489 return RegistryValueKind.Unknown;
490 object value = values [name];
492 return RegistryValueKind.Unknown;
495 return RegistryValueKind.DWord;
496 if (value is string [])
497 return RegistryValueKind.MultiString;
499 return RegistryValueKind.QWord;
500 if (value is byte [])
501 return RegistryValueKind.Binary;
503 return RegistryValueKind.String;
504 if (value is ExpandString)
505 return RegistryValueKind.ExpandString;
506 return RegistryValueKind.Unknown;
509 public object GetValue (string name, RegistryValueOptions options)
511 if (IsMarkedForDeletion)
516 object value = values [name];
517 ExpandString exp = value as ExpandString;
520 if ((options & RegistryValueOptions.DoNotExpandEnvironmentNames) == 0)
521 return exp.Expand ();
523 return exp.ToString ();
526 public void SetValue (string name, object value)
528 AssertNotMarkedForDeletion ();
533 // immediately convert non-native registry values to string to avoid
534 // returning it unmodified in calls to UnixRegistryApi.GetValue
535 if (value is int || value is string || value is byte[] || value is string[])
536 values[name] = value;
538 values[name] = value.ToString ();
542 public string [] GetValueNames ()
544 AssertNotMarkedForDeletion ();
546 ICollection keys = values.Keys;
548 string [] vals = new string [keys.Count];
549 keys.CopyTo (vals, 0);
553 public int GetSubKeyCount ()
555 return GetSubKeyNames ().Length;
558 public string [] GetSubKeyNames ()
560 DirectoryInfo selfDir = new DirectoryInfo (ActualDir);
561 DirectoryInfo[] subDirs = selfDir.GetDirectories ();
562 string[] subKeyNames;
564 // for volatile keys (cannot contain non-volatile subkeys) or keys
565 // without *any* presence in the volatile key section, we can do it simple.
566 if (IsVolatile || !Directory.Exists (GetVolatileDir (Dir))) {
567 subKeyNames = new string[subDirs.Length];
568 for (int i = 0; i < subDirs.Length; i++) {
569 DirectoryInfo subDir = subDirs[i];
570 subKeyNames[i] = subDir.Name;
575 // We may have the entries repeated, so keep just one of each one.
576 DirectoryInfo volatileDir = new DirectoryInfo (GetVolatileDir (Dir));
577 DirectoryInfo [] volatileSubDirs = volatileDir.GetDirectories ();
578 Dictionary<string,string> dirs = new Dictionary<string,string> ();
580 foreach (DirectoryInfo dir in subDirs)
581 dirs [dir.Name] = dir.Name;
582 foreach (DirectoryInfo volDir in volatileSubDirs)
583 dirs [volDir.Name] = volDir.Name;
585 subKeyNames = new string [dirs.Count];
587 foreach (KeyValuePair<string,string> entry in dirs)
588 subKeyNames[j++] = entry.Value;
594 // This version has to do argument validation based on the valueKind
596 public void SetValue (string name, object value, RegistryValueKind valueKind)
604 case RegistryValueKind.String:
605 if (value is string){
606 values [name] = value;
610 case RegistryValueKind.ExpandString:
611 if (value is string){
612 values [name] = new ExpandString ((string)value);
617 case RegistryValueKind.Binary:
618 if (value is byte []){
619 values [name] = value;
624 case RegistryValueKind.DWord:
626 values [name] = Convert.ToInt32 (value);
628 } catch (OverflowException) {
632 case RegistryValueKind.MultiString:
633 if (value is string []){
634 values [name] = value;
639 case RegistryValueKind.QWord:
641 values [name] = Convert.ToInt64 (value);
643 } catch (OverflowException) {
648 throw new ArgumentException ("unknown value", "valueKind");
650 throw new ArgumentException ("Value could not be converted to specified type", "valueKind");
655 lock (typeof (KeyHandler)){
659 new Timer (DirtyTimeout, null, 3000, Timeout.Infinite);
663 public void DirtyTimeout (object state)
670 lock (typeof (KeyHandler)) {
678 public bool ValueExists (string name)
683 return values.Contains (name);
686 public int ValueCount {
688 return values.Keys.Count;
692 public bool IsMarkedForDeletion {
694 return !dir_to_handler.Contains (Dir);
698 public void RemoveValue (string name)
700 AssertNotMarkedForDeletion ();
702 values.Remove (name);
713 if (IsMarkedForDeletion)
716 if (!File.Exists (file) && values.Count == 0)
719 SecurityElement se = new SecurityElement ("values");
721 // With SecurityElement.Text = value, and SecurityElement.AddAttribute(key, value)
722 // the values must be escaped prior to being assigned.
723 foreach (DictionaryEntry de in values){
724 object val = de.Value;
725 SecurityElement value = new SecurityElement ("value");
726 value.AddAttribute ("name", SecurityElement.Escape ((string) de.Key));
729 value.AddAttribute ("type", "string");
730 value.Text = SecurityElement.Escape ((string) val);
731 } else if (val is int){
732 value.AddAttribute ("type", "int");
733 value.Text = val.ToString ();
734 } else if (val is long) {
735 value.AddAttribute ("type", "qword");
736 value.Text = val.ToString ();
737 } else if (val is byte []){
738 value.AddAttribute ("type", "bytearray");
739 value.Text = Convert.ToBase64String ((byte[]) val);
740 } else if (val is ExpandString){
741 value.AddAttribute ("type", "expand");
742 value.Text = SecurityElement.Escape (val.ToString ());
743 } else if (val is string []){
744 value.AddAttribute ("type", "string-array");
746 foreach (string ss in (string[]) val){
747 SecurityElement str = new SecurityElement ("string");
748 str.Text = SecurityElement.Escape (ss);
749 value.AddChild (str);
755 using (FileStream fs = File.Create (file)){
756 StreamWriter sw = new StreamWriter (fs);
758 sw.Write (se.ToString ());
763 private void AssertNotMarkedForDeletion ()
765 if (IsMarkedForDeletion)
766 throw RegistryKey.CreateMarkedForDeletionException ();
769 static string user_store;
770 static string machine_store;
772 private static string UserStore {
774 if (user_store == null)
775 user_store = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal),
782 private static string MachineStore {
784 if (machine_store == null) {
785 machine_store = Environment.GetEnvironmentVariable ("MONO_REGISTRY_PATH");
786 if (machine_store == null) {
787 string s = Environment.GetMachineConfigPath ();
788 int p = s.IndexOf ("machine.config");
789 machine_store = Path.Combine (Path.Combine (s.Substring (0, p-1), ".."), "registry");
793 return machine_store;
798 internal class UnixRegistryApi : IRegistryApi {
800 static string ToUnix (string keyname)
802 if (keyname.IndexOf ('\\') != -1)
803 keyname = keyname.Replace ('\\', '/');
804 return keyname.ToLower ();
807 static bool IsWellKnownKey (string parentKeyName, string keyname)
809 // FIXME: Add more keys if needed
810 if (parentKeyName == Registry.CurrentUser.Name ||
811 parentKeyName == Registry.LocalMachine.Name)
812 return (0 == String.Compare ("software", keyname, true, CultureInfo.InvariantCulture));
817 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname)
819 return CreateSubKey (rkey, keyname, true);
823 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname, RegistryOptions options)
825 return CreateSubKey (rkey, keyname, true, options == RegistryOptions.Volatile);
829 public RegistryKey OpenRemoteBaseKey (RegistryHive hKey, string machineName)
831 throw new NotImplementedException ();
834 public RegistryKey OpenSubKey (RegistryKey rkey, string keyname, bool writable)
836 KeyHandler self = KeyHandler.Lookup (rkey, true);
838 // return null if parent is marked for deletion
842 RegistryKey result = self.Probe (rkey, ToUnix (keyname), writable);
843 if (result == null && IsWellKnownKey (rkey.Name, keyname)) {
844 // create the subkey even if its parent was opened read-only
845 result = CreateSubKey (rkey, keyname, writable);
852 public RegistryKey FromHandle (SafeRegistryHandle handle)
854 throw new NotImplementedException ();
858 public void Flush (RegistryKey rkey)
860 KeyHandler self = KeyHandler.Lookup (rkey, false);
862 // we do not need to flush changes as key is marked for deletion
868 public void Close (RegistryKey rkey)
870 KeyHandler.Drop (rkey);
873 public object GetValue (RegistryKey rkey, string name, object default_value, RegistryValueOptions options)
875 KeyHandler self = KeyHandler.Lookup (rkey, true);
877 // key was removed since it was opened
878 return default_value;
881 if (self.ValueExists (name))
882 return self.GetValue (name, options);
883 return default_value;
886 public void SetValue (RegistryKey rkey, string name, object value)
888 KeyHandler self = KeyHandler.Lookup (rkey, true);
890 throw RegistryKey.CreateMarkedForDeletionException ();
891 self.SetValue (name, value);
894 public void SetValue (RegistryKey rkey, string name, object value, RegistryValueKind valueKind)
896 KeyHandler self = KeyHandler.Lookup (rkey, true);
898 throw RegistryKey.CreateMarkedForDeletionException ();
899 self.SetValue (name, value, valueKind);
902 public int SubKeyCount (RegistryKey rkey)
904 KeyHandler self = KeyHandler.Lookup (rkey, true);
906 throw RegistryKey.CreateMarkedForDeletionException ();
907 return self.GetSubKeyCount ();
910 public int ValueCount (RegistryKey rkey)
912 KeyHandler self = KeyHandler.Lookup (rkey, true);
914 throw RegistryKey.CreateMarkedForDeletionException ();
915 return self.ValueCount;
918 public void DeleteValue (RegistryKey rkey, string name, bool throw_if_missing)
920 KeyHandler self = KeyHandler.Lookup (rkey, true);
922 // if key is marked for deletion, report success regardless of
927 if (throw_if_missing && !self.ValueExists (name))
928 throw new ArgumentException ("the given value does not exist");
930 self.RemoveValue (name);
933 public void DeleteKey (RegistryKey rkey, string keyname, bool throw_if_missing)
935 KeyHandler self = KeyHandler.Lookup (rkey, true);
937 // key is marked for deletion
938 if (!throw_if_missing)
940 throw new ArgumentException ("the given value does not exist");
943 string dir = Path.Combine (self.Dir, ToUnix (keyname));
945 if (!KeyHandler.Delete (dir) && throw_if_missing)
946 throw new ArgumentException ("the given value does not exist");
949 public string [] GetSubKeyNames (RegistryKey rkey)
951 KeyHandler self = KeyHandler.Lookup (rkey, true);
952 return self.GetSubKeyNames ();
955 public string [] GetValueNames (RegistryKey rkey)
957 KeyHandler self = KeyHandler.Lookup (rkey, true);
959 throw RegistryKey.CreateMarkedForDeletionException ();
960 return self.GetValueNames ();
963 public string ToString (RegistryKey rkey)
968 private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable)
970 return CreateSubKey (rkey, keyname, writable, false);
973 private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable, bool is_volatile)
975 KeyHandler self = KeyHandler.Lookup (rkey, true);
977 throw RegistryKey.CreateMarkedForDeletionException ();
978 if (KeyHandler.VolatileKeyExists (self.Dir) && !is_volatile)
979 throw new IOException ("Cannot create a non volatile subkey under a volatile key.");
981 return self.Ensure (rkey, ToUnix (keyname), writable, is_volatile);
984 public RegistryValueKind GetValueKind (RegistryKey rkey, string name)
986 KeyHandler self = KeyHandler.Lookup (rkey, true);
988 return self.GetValueKind (name);
990 // key was removed since it was opened or it does not exist.
991 return RegistryValueKind.Unknown;
995 public IntPtr GetHandle (RegistryKey key)
997 throw new NotImplementedException ();