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){
138 throw new SecurityException ("No access to the given key");
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 e) {
332 Console.Error.WriteLine ("While saving registry data at {0}: {1}", path, e);
336 // We save the last boot time in a last-btime file in every root, and we use it
337 // to clean the volatile keys directory in case the system btime changed.
338 static void CleanVolatileKeys ()
340 long system_btime = GetSystemBootTime ();
342 string [] roots = new string [] {
347 foreach (string root in roots) {
348 if (!Directory.Exists (root))
351 string btime_file = Path.Combine (root, "last-btime");
352 string volatile_dir = Path.Combine (root, VolatileDirectoryName);
354 if (Directory.Exists (volatile_dir)) {
355 long registered_btime = GetRegisteredBootTime (btime_file);
356 if (system_btime < 0 || registered_btime < 0 || registered_btime != system_btime)
357 Directory.Delete (volatile_dir, true);
360 SaveRegisteredBootTime (btime_file, system_btime);
364 public static bool VolatileKeyExists (string dir)
366 lock (typeof (KeyHandler)) {
367 KeyHandler kh = (KeyHandler) dir_to_handler [dir];
369 return kh.IsVolatile;
372 if (Directory.Exists (dir)) // Non-volatile key exists.
375 return Directory.Exists (GetVolatileDir (dir));
378 public static string GetVolatileDir (string dir)
380 string root = GetRootFromDir (dir);
381 string volatile_dir = dir.Replace (root, Path.Combine (root, VolatileDirectoryName));
385 public static KeyHandler Lookup (RegistryKey rkey, bool createNonExisting)
387 lock (typeof (KeyHandler)){
388 KeyHandler k = (KeyHandler) key_to_handler [rkey];
392 // when a non-root key is requested for no keyhandler exist
393 // then that key must have been marked for deletion
394 if (!rkey.IsRoot || !createNonExisting)
397 RegistryHive x = (RegistryHive) rkey.Hive;
399 case RegistryHive.CurrentUser:
400 string userDir = Path.Combine (UserStore, x.ToString ());
401 k = new KeyHandler (rkey, userDir);
402 dir_to_handler [userDir] = k;
404 case RegistryHive.CurrentConfig:
405 case RegistryHive.ClassesRoot:
406 case RegistryHive.DynData:
407 case RegistryHive.LocalMachine:
408 case RegistryHive.PerformanceData:
409 case RegistryHive.Users:
410 string machine_dir = Path.Combine (MachineStore, x.ToString ());
411 k = new KeyHandler (rkey, machine_dir);
412 dir_to_handler [machine_dir] = k;
415 throw new Exception ("Unknown RegistryHive");
417 key_to_handler [rkey] = k;
422 static string GetRootFromDir (string dir)
424 if (dir.IndexOf (UserStore) > -1)
426 else if (dir.IndexOf (MachineStore) > -1)
429 throw new Exception ("Could not get root for dir " + dir);
432 public static void Drop (RegistryKey rkey)
434 lock (typeof (KeyHandler)) {
435 KeyHandler k = (KeyHandler) key_to_handler [rkey];
438 key_to_handler.Remove (rkey);
440 // remove cached KeyHandler if no other keys reference it
442 foreach (DictionaryEntry de in key_to_handler)
446 dir_to_handler.Remove (k.Dir);
450 public static void Drop (string dir)
452 lock (typeof (KeyHandler)) {
453 KeyHandler kh = (KeyHandler) dir_to_handler [dir];
457 dir_to_handler.Remove (dir);
459 // remove (other) references to keyhandler
460 ArrayList keys = new ArrayList ();
461 foreach (DictionaryEntry de in key_to_handler)
465 foreach (object key in keys)
466 key_to_handler.Remove (key);
470 public static bool Delete (string dir)
472 if (!Directory.Exists (dir)) {
473 string volatile_dir = GetVolatileDir (dir);
474 if (!Directory.Exists (volatile_dir))
480 Directory.Delete (dir, true);
485 public RegistryValueKind GetValueKind (string name)
488 return RegistryValueKind.Unknown;
489 object value = values [name];
491 return RegistryValueKind.Unknown;
494 return RegistryValueKind.DWord;
495 if (value is string [])
496 return RegistryValueKind.MultiString;
498 return RegistryValueKind.QWord;
499 if (value is byte [])
500 return RegistryValueKind.Binary;
502 return RegistryValueKind.String;
503 if (value is ExpandString)
504 return RegistryValueKind.ExpandString;
505 return RegistryValueKind.Unknown;
508 public object GetValue (string name, RegistryValueOptions options)
510 if (IsMarkedForDeletion)
515 object value = values [name];
516 ExpandString exp = value as ExpandString;
519 if ((options & RegistryValueOptions.DoNotExpandEnvironmentNames) == 0)
520 return exp.Expand ();
522 return exp.ToString ();
525 public void SetValue (string name, object value)
527 AssertNotMarkedForDeletion ();
532 // immediately convert non-native registry values to string to avoid
533 // returning it unmodified in calls to UnixRegistryApi.GetValue
534 if (value is int || value is string || value is byte[] || value is string[])
535 values[name] = value;
537 values[name] = value.ToString ();
541 public string [] GetValueNames ()
543 AssertNotMarkedForDeletion ();
545 ICollection keys = values.Keys;
547 string [] vals = new string [keys.Count];
548 keys.CopyTo (vals, 0);
552 public int GetSubKeyCount ()
554 return GetSubKeyNames ().Length;
557 public string [] GetSubKeyNames ()
559 DirectoryInfo selfDir = new DirectoryInfo (ActualDir);
560 DirectoryInfo[] subDirs = selfDir.GetDirectories ();
561 string[] subKeyNames;
563 // for volatile keys (cannot contain non-volatile subkeys) or keys
564 // without *any* presence in the volatile key section, we can do it simple.
565 if (IsVolatile || !Directory.Exists (GetVolatileDir (Dir))) {
566 subKeyNames = new string[subDirs.Length];
567 for (int i = 0; i < subDirs.Length; i++) {
568 DirectoryInfo subDir = subDirs[i];
569 subKeyNames[i] = subDir.Name;
574 // We may have the entries repeated, so keep just one of each one.
575 DirectoryInfo volatileDir = new DirectoryInfo (GetVolatileDir (Dir));
576 DirectoryInfo [] volatileSubDirs = volatileDir.GetDirectories ();
577 Dictionary<string,string> dirs = new Dictionary<string,string> ();
579 foreach (DirectoryInfo dir in subDirs)
580 dirs [dir.Name] = dir.Name;
581 foreach (DirectoryInfo volDir in volatileSubDirs)
582 dirs [volDir.Name] = volDir.Name;
584 subKeyNames = new string [dirs.Count];
586 foreach (KeyValuePair<string,string> entry in dirs)
587 subKeyNames[j++] = entry.Value;
593 // This version has to do argument validation based on the valueKind
595 public void SetValue (string name, object value, RegistryValueKind valueKind)
603 case RegistryValueKind.String:
604 if (value is string){
605 values [name] = value;
609 case RegistryValueKind.ExpandString:
610 if (value is string){
611 values [name] = new ExpandString ((string)value);
616 case RegistryValueKind.Binary:
617 if (value is byte []){
618 values [name] = value;
623 case RegistryValueKind.DWord:
625 values [name] = Convert.ToInt32 (value);
627 } catch (OverflowException) {
631 case RegistryValueKind.MultiString:
632 if (value is string []){
633 values [name] = value;
638 case RegistryValueKind.QWord:
640 values [name] = Convert.ToInt64 (value);
642 } catch (OverflowException) {
647 throw new ArgumentException ("unknown value", "valueKind");
649 throw new ArgumentException ("Value could not be converted to specified type", "valueKind");
654 lock (typeof (KeyHandler)){
658 new Timer (DirtyTimeout, null, 3000, Timeout.Infinite);
662 public void DirtyTimeout (object state)
669 lock (typeof (KeyHandler)) {
677 public bool ValueExists (string name)
682 return values.Contains (name);
685 public int ValueCount {
687 return values.Keys.Count;
691 public bool IsMarkedForDeletion {
693 return !dir_to_handler.Contains (Dir);
697 public void RemoveValue (string name)
699 AssertNotMarkedForDeletion ();
701 values.Remove (name);
712 if (IsMarkedForDeletion)
715 if (!File.Exists (file) && values.Count == 0)
718 SecurityElement se = new SecurityElement ("values");
720 // With SecurityElement.Text = value, and SecurityElement.AddAttribute(key, value)
721 // the values must be escaped prior to being assigned.
722 foreach (DictionaryEntry de in values){
723 object val = de.Value;
724 SecurityElement value = new SecurityElement ("value");
725 value.AddAttribute ("name", SecurityElement.Escape ((string) de.Key));
728 value.AddAttribute ("type", "string");
729 value.Text = SecurityElement.Escape ((string) val);
730 } else if (val is int){
731 value.AddAttribute ("type", "int");
732 value.Text = val.ToString ();
733 } else if (val is long) {
734 value.AddAttribute ("type", "qword");
735 value.Text = val.ToString ();
736 } else if (val is byte []){
737 value.AddAttribute ("type", "bytearray");
738 value.Text = Convert.ToBase64String ((byte[]) val);
739 } else if (val is ExpandString){
740 value.AddAttribute ("type", "expand");
741 value.Text = SecurityElement.Escape (val.ToString ());
742 } else if (val is string []){
743 value.AddAttribute ("type", "string-array");
745 foreach (string ss in (string[]) val){
746 SecurityElement str = new SecurityElement ("string");
747 str.Text = SecurityElement.Escape (ss);
748 value.AddChild (str);
754 using (FileStream fs = File.Create (file)){
755 StreamWriter sw = new StreamWriter (fs);
757 sw.Write (se.ToString ());
762 private void AssertNotMarkedForDeletion ()
764 if (IsMarkedForDeletion)
765 throw RegistryKey.CreateMarkedForDeletionException ();
768 static string user_store;
769 static string machine_store;
771 private static string UserStore {
773 if (user_store == null) {
774 user_store = Environment.GetEnvironmentVariable ("MONO_CURRENTUSER_REGISTRY_PATH");
775 if (string.IsNullOrEmpty(user_store)) {
776 user_store = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal),
785 private static string MachineStore {
787 if (machine_store == null) {
788 machine_store = Environment.GetEnvironmentVariable ("MONO_REGISTRY_PATH");
789 if (machine_store == null) {
790 string s = Environment.GetMachineConfigPath ();
791 int p = s.IndexOf ("machine.config");
792 machine_store = Path.Combine (Path.Combine (s.Substring (0, p-1), ".."), "registry");
796 return machine_store;
801 internal class UnixRegistryApi : IRegistryApi {
803 static string ToUnix (string keyname)
805 if (keyname.IndexOf ('\\') != -1)
806 keyname = keyname.Replace ('\\', '/');
807 return keyname.ToLower ();
810 static bool IsWellKnownKey (string parentKeyName, string keyname)
812 // FIXME: Add more keys if needed
813 if (parentKeyName == Registry.CurrentUser.Name ||
814 parentKeyName == Registry.LocalMachine.Name)
815 return (0 == String.Compare ("software", keyname, true, CultureInfo.InvariantCulture));
820 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname)
822 return CreateSubKey (rkey, keyname, true);
826 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname, RegistryOptions options)
828 return CreateSubKey (rkey, keyname, true, options == RegistryOptions.Volatile);
832 public RegistryKey OpenRemoteBaseKey (RegistryHive hKey, string machineName)
834 throw new NotImplementedException ();
837 public RegistryKey OpenSubKey (RegistryKey rkey, string keyname, bool writable)
839 KeyHandler self = KeyHandler.Lookup (rkey, true);
841 // return null if parent is marked for deletion
845 RegistryKey result = self.Probe (rkey, ToUnix (keyname), writable);
846 if (result == null && IsWellKnownKey (rkey.Name, keyname)) {
847 // create the subkey even if its parent was opened read-only
848 result = CreateSubKey (rkey, keyname, writable);
855 public RegistryKey FromHandle (SafeRegistryHandle handle)
857 throw new NotImplementedException ();
861 public void Flush (RegistryKey rkey)
863 KeyHandler self = KeyHandler.Lookup (rkey, false);
865 // we do not need to flush changes as key is marked for deletion
871 public void Close (RegistryKey rkey)
873 KeyHandler.Drop (rkey);
876 public object GetValue (RegistryKey rkey, string name, object default_value, RegistryValueOptions options)
878 KeyHandler self = KeyHandler.Lookup (rkey, true);
880 // key was removed since it was opened
881 return default_value;
884 if (self.ValueExists (name))
885 return self.GetValue (name, options);
886 return default_value;
889 public void SetValue (RegistryKey rkey, string name, object value)
891 KeyHandler self = KeyHandler.Lookup (rkey, true);
893 throw RegistryKey.CreateMarkedForDeletionException ();
894 self.SetValue (name, value);
897 public void SetValue (RegistryKey rkey, string name, object value, RegistryValueKind valueKind)
899 KeyHandler self = KeyHandler.Lookup (rkey, true);
901 throw RegistryKey.CreateMarkedForDeletionException ();
902 self.SetValue (name, value, valueKind);
905 public int SubKeyCount (RegistryKey rkey)
907 KeyHandler self = KeyHandler.Lookup (rkey, true);
909 throw RegistryKey.CreateMarkedForDeletionException ();
910 return self.GetSubKeyCount ();
913 public int ValueCount (RegistryKey rkey)
915 KeyHandler self = KeyHandler.Lookup (rkey, true);
917 throw RegistryKey.CreateMarkedForDeletionException ();
918 return self.ValueCount;
921 public void DeleteValue (RegistryKey rkey, string name, bool throw_if_missing)
923 KeyHandler self = KeyHandler.Lookup (rkey, true);
925 // if key is marked for deletion, report success regardless of
930 if (throw_if_missing && !self.ValueExists (name))
931 throw new ArgumentException ("the given value does not exist");
933 self.RemoveValue (name);
936 public void DeleteKey (RegistryKey rkey, string keyname, bool throw_if_missing)
938 KeyHandler self = KeyHandler.Lookup (rkey, true);
940 // key is marked for deletion
941 if (!throw_if_missing)
943 throw new ArgumentException ("the given value does not exist");
946 string dir = Path.Combine (self.Dir, ToUnix (keyname));
948 if (!KeyHandler.Delete (dir) && throw_if_missing)
949 throw new ArgumentException ("the given value does not exist");
952 public string [] GetSubKeyNames (RegistryKey rkey)
954 KeyHandler self = KeyHandler.Lookup (rkey, true);
955 return self.GetSubKeyNames ();
958 public string [] GetValueNames (RegistryKey rkey)
960 KeyHandler self = KeyHandler.Lookup (rkey, true);
962 throw RegistryKey.CreateMarkedForDeletionException ();
963 return self.GetValueNames ();
966 public string ToString (RegistryKey rkey)
971 private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable)
973 return CreateSubKey (rkey, keyname, writable, false);
976 private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable, bool is_volatile)
978 KeyHandler self = KeyHandler.Lookup (rkey, true);
980 throw RegistryKey.CreateMarkedForDeletionException ();
981 if (KeyHandler.VolatileKeyExists (self.Dir) && !is_volatile)
982 throw new IOException ("Cannot create a non volatile subkey under a volatile key.");
984 return self.Ensure (rkey, ToUnix (keyname), writable, is_volatile);
987 public RegistryValueKind GetValueKind (RegistryKey rkey, string name)
989 KeyHandler self = KeyHandler.Lookup (rkey, true);
991 return self.GetValueKind (name);
993 // key was removed since it was opened or it does not exist.
994 return RegistryValueKind.Unknown;
998 public IntPtr GetHandle (RegistryKey key)
1000 throw new NotImplementedException ();