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 (((long) value) < Int32.MaxValue) &&
626 (((long) value) > Int32.MinValue)){
627 values [name] = (int) ((long)value);
631 values [name] = value;
636 case RegistryValueKind.MultiString:
637 if (value is string []){
638 values [name] = value;
643 case RegistryValueKind.QWord:
645 values [name] = (long) ((int) value);
649 values [name] = value;
654 throw new ArgumentException ("unknown value", "valueKind");
656 throw new ArgumentException ("Value could not be converted to specified type", "valueKind");
661 lock (typeof (KeyHandler)){
665 new Timer (DirtyTimeout, null, 3000, Timeout.Infinite);
669 public void DirtyTimeout (object state)
676 lock (typeof (KeyHandler)) {
684 public bool ValueExists (string name)
689 return values.Contains (name);
692 public int ValueCount {
694 return values.Keys.Count;
698 public bool IsMarkedForDeletion {
700 return !dir_to_handler.Contains (Dir);
704 public void RemoveValue (string name)
706 AssertNotMarkedForDeletion ();
708 values.Remove (name);
719 if (IsMarkedForDeletion)
722 if (!File.Exists (file) && values.Count == 0)
725 SecurityElement se = new SecurityElement ("values");
727 // With SecurityElement.Text = value, and SecurityElement.AddAttribute(key, value)
728 // the values must be escaped prior to being assigned.
729 foreach (DictionaryEntry de in values){
730 object val = de.Value;
731 SecurityElement value = new SecurityElement ("value");
732 value.AddAttribute ("name", SecurityElement.Escape ((string) de.Key));
735 value.AddAttribute ("type", "string");
736 value.Text = SecurityElement.Escape ((string) val);
737 } else if (val is int){
738 value.AddAttribute ("type", "int");
739 value.Text = val.ToString ();
740 } else if (val is long) {
741 value.AddAttribute ("type", "qword");
742 value.Text = val.ToString ();
743 } else if (val is byte []){
744 value.AddAttribute ("type", "bytearray");
745 value.Text = Convert.ToBase64String ((byte[]) val);
746 } else if (val is ExpandString){
747 value.AddAttribute ("type", "expand");
748 value.Text = SecurityElement.Escape (val.ToString ());
749 } else if (val is string []){
750 value.AddAttribute ("type", "string-array");
752 foreach (string ss in (string[]) val){
753 SecurityElement str = new SecurityElement ("string");
754 str.Text = SecurityElement.Escape (ss);
755 value.AddChild (str);
761 using (FileStream fs = File.Create (file)){
762 StreamWriter sw = new StreamWriter (fs);
764 sw.Write (se.ToString ());
769 private void AssertNotMarkedForDeletion ()
771 if (IsMarkedForDeletion)
772 throw RegistryKey.CreateMarkedForDeletionException ();
775 static string user_store;
776 static string machine_store;
778 private static string UserStore {
780 if (user_store == null)
781 user_store = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal),
788 private static string MachineStore {
790 if (machine_store == null) {
791 machine_store = Environment.GetEnvironmentVariable ("MONO_REGISTRY_PATH");
792 if (machine_store == null) {
793 string s = Environment.GetMachineConfigPath ();
794 int p = s.IndexOf ("machine.config");
795 machine_store = Path.Combine (Path.Combine (s.Substring (0, p-1), ".."), "registry");
799 return machine_store;
804 internal class UnixRegistryApi : IRegistryApi {
806 static string ToUnix (string keyname)
808 if (keyname.IndexOf ('\\') != -1)
809 keyname = keyname.Replace ('\\', '/');
810 return keyname.ToLower ();
813 static bool IsWellKnownKey (string parentKeyName, string keyname)
815 // FIXME: Add more keys if needed
816 if (parentKeyName == Registry.CurrentUser.Name ||
817 parentKeyName == Registry.LocalMachine.Name)
818 return (0 == String.Compare ("software", keyname, true, CultureInfo.InvariantCulture));
823 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname)
825 return CreateSubKey (rkey, keyname, true);
829 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname, RegistryOptions options)
831 return CreateSubKey (rkey, keyname, true, options == RegistryOptions.Volatile);
835 public RegistryKey OpenRemoteBaseKey (RegistryHive hKey, string machineName)
837 throw new NotImplementedException ();
840 public RegistryKey OpenSubKey (RegistryKey rkey, string keyname, bool writable)
842 KeyHandler self = KeyHandler.Lookup (rkey, true);
844 // return null if parent is marked for deletion
848 RegistryKey result = self.Probe (rkey, ToUnix (keyname), writable);
849 if (result == null && IsWellKnownKey (rkey.Name, keyname)) {
850 // create the subkey even if its parent was opened read-only
851 result = CreateSubKey (rkey, keyname, writable);
858 public RegistryKey FromHandle (SafeRegistryHandle handle)
860 throw new NotImplementedException ();
864 public void Flush (RegistryKey rkey)
866 KeyHandler self = KeyHandler.Lookup (rkey, false);
868 // we do not need to flush changes as key is marked for deletion
874 public void Close (RegistryKey rkey)
876 KeyHandler.Drop (rkey);
879 public object GetValue (RegistryKey rkey, string name, object default_value, RegistryValueOptions options)
881 KeyHandler self = KeyHandler.Lookup (rkey, true);
883 // key was removed since it was opened
884 return default_value;
887 if (self.ValueExists (name))
888 return self.GetValue (name, options);
889 return default_value;
892 public void SetValue (RegistryKey rkey, string name, object value)
894 KeyHandler self = KeyHandler.Lookup (rkey, true);
896 throw RegistryKey.CreateMarkedForDeletionException ();
897 self.SetValue (name, value);
900 public void SetValue (RegistryKey rkey, string name, object value, RegistryValueKind valueKind)
902 KeyHandler self = KeyHandler.Lookup (rkey, true);
904 throw RegistryKey.CreateMarkedForDeletionException ();
905 self.SetValue (name, value, valueKind);
908 public int SubKeyCount (RegistryKey rkey)
910 KeyHandler self = KeyHandler.Lookup (rkey, true);
912 throw RegistryKey.CreateMarkedForDeletionException ();
913 return self.GetSubKeyCount ();
916 public int ValueCount (RegistryKey rkey)
918 KeyHandler self = KeyHandler.Lookup (rkey, true);
920 throw RegistryKey.CreateMarkedForDeletionException ();
921 return self.ValueCount;
924 public void DeleteValue (RegistryKey rkey, string name, bool throw_if_missing)
926 KeyHandler self = KeyHandler.Lookup (rkey, true);
928 // if key is marked for deletion, report success regardless of
933 if (throw_if_missing && !self.ValueExists (name))
934 throw new ArgumentException ("the given value does not exist");
936 self.RemoveValue (name);
939 public void DeleteKey (RegistryKey rkey, string keyname, bool throw_if_missing)
941 KeyHandler self = KeyHandler.Lookup (rkey, true);
943 // key is marked for deletion
944 if (!throw_if_missing)
946 throw new ArgumentException ("the given value does not exist");
949 string dir = Path.Combine (self.Dir, ToUnix (keyname));
951 if (!KeyHandler.Delete (dir) && throw_if_missing)
952 throw new ArgumentException ("the given value does not exist");
955 public string [] GetSubKeyNames (RegistryKey rkey)
957 KeyHandler self = KeyHandler.Lookup (rkey, true);
958 return self.GetSubKeyNames ();
961 public string [] GetValueNames (RegistryKey rkey)
963 KeyHandler self = KeyHandler.Lookup (rkey, true);
965 throw RegistryKey.CreateMarkedForDeletionException ();
966 return self.GetValueNames ();
969 public string ToString (RegistryKey rkey)
974 private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable)
976 return CreateSubKey (rkey, keyname, writable, false);
979 private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable, bool is_volatile)
981 KeyHandler self = KeyHandler.Lookup (rkey, true);
983 throw RegistryKey.CreateMarkedForDeletionException ();
984 if (KeyHandler.VolatileKeyExists (self.Dir) && !is_volatile)
985 throw new IOException ("Cannot create a non volatile subkey under a volatile key.");
987 return self.Ensure (rkey, ToUnix (keyname), writable, is_volatile);
990 public RegistryValueKind GetValueKind (RegistryKey rkey, string name)
992 KeyHandler self = KeyHandler.Lookup (rkey, true);
994 return self.GetValueKind (name);
996 // key was removed since it was opened or it does not exist.
997 return RegistryValueKind.Unknown;
1001 public IntPtr GetHandle (RegistryKey key)
1003 throw new NotImplementedException ();