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 ());
105 KeyHandler (RegistryKey rkey, string basedir)
107 if (!Directory.Exists (basedir)){
109 Directory.CreateDirectory (basedir);
110 } catch (UnauthorizedAccessException){
111 throw new SecurityException ("No access to the given key");
115 file = Path.Combine (Dir, "values.xml");
121 values = new Hashtable ();
122 if (!File.Exists (file))
126 using (FileStream fs = File.OpenRead (file)){
127 StreamReader r = new StreamReader (fs);
128 string xml = r.ReadToEnd ();
132 SecurityElement tree = SecurityElement.FromString (xml);
133 if (tree.Tag == "values" && tree.Children != null){
134 foreach (SecurityElement value in tree.Children){
135 if (value.Tag == "value"){
141 } catch (UnauthorizedAccessException){
143 throw new SecurityException ("No access to the given key");
144 } catch (Exception e){
145 Console.Error.WriteLine ("While loading registry key at {0}: {1}", file, e);
150 void LoadKey (SecurityElement se)
152 Hashtable h = se.Attributes;
154 string name = (string) h ["name"];
157 string type = (string) h ["type"];
163 values [name] = Int32.Parse (se.Text);
166 values [name] = Convert.FromBase64String (se.Text);
169 values [name] = se.Text == null ? String.Empty : se.Text;
172 values [name] = new ExpandString (se.Text);
175 values [name] = Int64.Parse (se.Text);
178 ArrayList sa = new ArrayList ();
179 if (se.Children != null){
180 foreach (SecurityElement stre in se.Children){
184 values [name] = sa.ToArray (typeof (string));
188 // We ignore individual errors in the file.
192 public RegistryKey Ensure (RegistryKey rkey, string extra, bool writable)
194 lock (typeof (KeyHandler)){
195 string f = Path.Combine (Dir, extra);
196 KeyHandler kh = (KeyHandler) dir_to_handler [f];
198 kh = new KeyHandler (rkey, f);
199 RegistryKey rk = new RegistryKey (kh, CombineName (rkey, extra), writable);
200 key_to_handler [rk] = kh;
201 dir_to_handler [f] = kh;
206 public RegistryKey Probe (RegistryKey rkey, string extra, bool writable)
208 RegistryKey rk = null;
210 lock (typeof (KeyHandler)){
211 string f = Path.Combine (Dir, extra);
212 KeyHandler kh = (KeyHandler) dir_to_handler [f];
214 rk = new RegistryKey (kh, CombineName (rkey,
216 key_to_handler [rk] = kh;
217 } else if (Directory.Exists (f)) {
218 kh = new KeyHandler (rkey, f);
219 rk = new RegistryKey (kh, CombineName (rkey, extra),
221 dir_to_handler [f] = kh;
222 key_to_handler [rk] = kh;
228 static string CombineName (RegistryKey rkey, string extra)
230 if (extra.IndexOf ('/') != -1)
231 extra = extra.Replace ('/', '\\');
233 return String.Concat (rkey.Name, "\\", extra);
236 public static KeyHandler Lookup (RegistryKey rkey, bool createNonExisting)
238 lock (typeof (KeyHandler)){
239 KeyHandler k = (KeyHandler) key_to_handler [rkey];
243 // when a non-root key is requested for no keyhandler exist
244 // then that key must have been marked for deletion
245 if (!rkey.IsRoot || !createNonExisting)
248 RegistryHive x = (RegistryHive) rkey.Hive;
250 case RegistryHive.CurrentUser:
251 string userDir = Path.Combine (UserStore, x.ToString ());
252 k = new KeyHandler (rkey, userDir);
253 dir_to_handler [userDir] = k;
255 case RegistryHive.CurrentConfig:
256 case RegistryHive.ClassesRoot:
257 case RegistryHive.DynData:
258 case RegistryHive.LocalMachine:
259 case RegistryHive.PerformanceData:
260 case RegistryHive.Users:
261 string machine_dir = Path.Combine (MachineStore, x.ToString ());
262 k = new KeyHandler (rkey, machine_dir);
263 dir_to_handler [machine_dir] = k;
266 throw new Exception ("Unknown RegistryHive");
268 key_to_handler [rkey] = k;
273 public static void Drop (RegistryKey rkey)
275 lock (typeof (KeyHandler)) {
276 KeyHandler k = (KeyHandler) key_to_handler [rkey];
279 key_to_handler.Remove (rkey);
281 // remove cached KeyHandler if no other keys reference it
283 foreach (DictionaryEntry de in key_to_handler)
287 dir_to_handler.Remove (k.Dir);
291 public static void Drop (string dir)
293 lock (typeof (KeyHandler)) {
294 KeyHandler kh = (KeyHandler) dir_to_handler [dir];
298 dir_to_handler.Remove (dir);
300 // remove (other) references to keyhandler
301 ArrayList keys = new ArrayList ();
302 foreach (DictionaryEntry de in key_to_handler)
306 foreach (object key in keys)
307 key_to_handler.Remove (key);
311 public RegistryValueKind GetValueKind (string name)
314 return RegistryValueKind.Unknown;
315 object value = values [name];
317 return RegistryValueKind.Unknown;
320 return RegistryValueKind.DWord;
321 if (value is string [])
322 return RegistryValueKind.MultiString;
324 return RegistryValueKind.QWord;
325 if (value is byte [])
326 return RegistryValueKind.Binary;
328 return RegistryValueKind.String;
329 if (value is ExpandString)
330 return RegistryValueKind.ExpandString;
331 return RegistryValueKind.Unknown;
334 public object GetValue (string name, RegistryValueOptions options)
336 if (IsMarkedForDeletion)
341 object value = values [name];
342 ExpandString exp = value as ExpandString;
345 if ((options & RegistryValueOptions.DoNotExpandEnvironmentNames) == 0)
346 return exp.Expand ();
348 return exp.ToString ();
351 public void SetValue (string name, object value)
353 AssertNotMarkedForDeletion ();
358 // immediately convert non-native registry values to string to avoid
359 // returning it unmodified in calls to UnixRegistryApi.GetValue
360 if (value is int || value is string || value is byte[] || value is string[])
361 values[name] = value;
363 values[name] = value.ToString ();
367 public string [] GetValueNames ()
369 AssertNotMarkedForDeletion ();
371 ICollection keys = values.Keys;
373 string [] vals = new string [keys.Count];
374 keys.CopyTo (vals, 0);
379 // This version has to do argument validation based on the valueKind
381 public void SetValue (string name, object value, RegistryValueKind valueKind)
389 case RegistryValueKind.String:
390 if (value is string){
391 values [name] = value;
395 case RegistryValueKind.ExpandString:
396 if (value is string){
397 values [name] = new ExpandString ((string)value);
402 case RegistryValueKind.Binary:
403 if (value is byte []){
404 values [name] = value;
409 case RegistryValueKind.DWord:
411 (((long) value) < Int32.MaxValue) &&
412 (((long) value) > Int32.MinValue)){
413 values [name] = (int) ((long)value);
417 values [name] = value;
422 case RegistryValueKind.MultiString:
423 if (value is string []){
424 values [name] = value;
429 case RegistryValueKind.QWord:
431 values [name] = (long) ((int) value);
435 values [name] = value;
440 throw new ArgumentException ("unknown value", "valueKind");
442 throw new ArgumentException ("Value could not be converted to specified type", "valueKind");
447 lock (typeof (KeyHandler)){
451 new Timer (DirtyTimeout, null, 3000, Timeout.Infinite);
455 public void DirtyTimeout (object state)
462 lock (typeof (KeyHandler)) {
470 public bool ValueExists (string name)
475 return values.Contains (name);
478 public int ValueCount {
480 return values.Keys.Count;
484 public bool IsMarkedForDeletion {
486 return !dir_to_handler.Contains (Dir);
490 public void RemoveValue (string name)
492 AssertNotMarkedForDeletion ();
494 values.Remove (name);
505 if (IsMarkedForDeletion)
508 if (!File.Exists (file) && values.Count == 0)
511 SecurityElement se = new SecurityElement ("values");
513 // With SecurityElement.Text = value, and SecurityElement.AddAttribute(key, value)
514 // the values must be escaped prior to being assigned.
515 foreach (DictionaryEntry de in values){
516 object val = de.Value;
517 SecurityElement value = new SecurityElement ("value");
518 value.AddAttribute ("name", SecurityElement.Escape ((string) de.Key));
521 value.AddAttribute ("type", "string");
522 value.Text = SecurityElement.Escape ((string) val);
523 } else if (val is int){
524 value.AddAttribute ("type", "int");
525 value.Text = val.ToString ();
526 } else if (val is long) {
527 value.AddAttribute ("type", "qword");
528 value.Text = val.ToString ();
529 } else if (val is byte []){
530 value.AddAttribute ("type", "bytearray");
531 value.Text = Convert.ToBase64String ((byte[]) val);
532 } else if (val is ExpandString){
533 value.AddAttribute ("type", "expand");
534 value.Text = SecurityElement.Escape (val.ToString ());
535 } else if (val is string []){
536 value.AddAttribute ("type", "string-array");
538 foreach (string ss in (string[]) val){
539 SecurityElement str = new SecurityElement ("string");
540 str.Text = SecurityElement.Escape (ss);
541 value.AddChild (str);
547 using (FileStream fs = File.Create (file)){
548 StreamWriter sw = new StreamWriter (fs);
550 sw.Write (se.ToString ());
555 private void AssertNotMarkedForDeletion ()
557 if (IsMarkedForDeletion)
558 throw RegistryKey.CreateMarkedForDeletionException ();
561 private static string UserStore {
563 return Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal),
568 private static string MachineStore {
572 s = Environment.GetEnvironmentVariable ("MONO_REGISTRY_PATH");
575 s = Environment.GetMachineConfigPath ();
576 int p = s.IndexOf ("machine.config");
577 return Path.Combine (Path.Combine (s.Substring (0, p-1), ".."), "registry");
582 internal class UnixRegistryApi : IRegistryApi {
584 static string ToUnix (string keyname)
586 if (keyname.IndexOf ('\\') != -1)
587 keyname = keyname.Replace ('\\', '/');
588 return keyname.ToLower ();
591 static bool IsWellKnownKey (string parentKeyName, string keyname)
593 // FIXME: Add more keys if needed
594 if (parentKeyName == Registry.CurrentUser.Name ||
595 parentKeyName == Registry.LocalMachine.Name)
596 return (0 == String.Compare ("software", keyname, true, CultureInfo.InvariantCulture));
601 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname)
603 return CreateSubKey (rkey, keyname, true);
606 public RegistryKey OpenRemoteBaseKey (RegistryHive hKey, string machineName)
608 throw new NotImplementedException ();
611 public RegistryKey OpenSubKey (RegistryKey rkey, string keyname, bool writable)
613 KeyHandler self = KeyHandler.Lookup (rkey, true);
615 // return null if parent is marked for deletion
619 RegistryKey result = self.Probe (rkey, ToUnix (keyname), writable);
620 if (result == null && IsWellKnownKey (rkey.Name, keyname)) {
621 // create the subkey even if its parent was opened read-only
622 result = CreateSubKey (rkey, keyname, writable);
628 public void Flush (RegistryKey rkey)
630 KeyHandler self = KeyHandler.Lookup (rkey, false);
632 // we do not need to flush changes as key is marked for deletion
638 public void Close (RegistryKey rkey)
640 KeyHandler.Drop (rkey);
643 public object GetValue (RegistryKey rkey, string name, object default_value, RegistryValueOptions options)
645 KeyHandler self = KeyHandler.Lookup (rkey, true);
647 // key was removed since it was opened
648 return default_value;
651 if (self.ValueExists (name))
652 return self.GetValue (name, options);
653 return default_value;
656 public void SetValue (RegistryKey rkey, string name, object value)
658 KeyHandler self = KeyHandler.Lookup (rkey, true);
660 throw RegistryKey.CreateMarkedForDeletionException ();
661 self.SetValue (name, value);
664 public void SetValue (RegistryKey rkey, string name, object value, RegistryValueKind valueKind)
666 KeyHandler self = KeyHandler.Lookup (rkey, true);
668 throw RegistryKey.CreateMarkedForDeletionException ();
669 self.SetValue (name, value, valueKind);
672 public int SubKeyCount (RegistryKey rkey)
674 KeyHandler self = KeyHandler.Lookup (rkey, true);
676 throw RegistryKey.CreateMarkedForDeletionException ();
677 return Directory.GetDirectories (self.Dir).Length;
680 public int ValueCount (RegistryKey rkey)
682 KeyHandler self = KeyHandler.Lookup (rkey, true);
684 throw RegistryKey.CreateMarkedForDeletionException ();
685 return self.ValueCount;
688 public void DeleteValue (RegistryKey rkey, string name, bool throw_if_missing)
690 KeyHandler self = KeyHandler.Lookup (rkey, true);
692 // if key is marked for deletion, report success regardless of
697 if (throw_if_missing && !self.ValueExists (name))
698 throw new ArgumentException ("the given value does not exist");
700 self.RemoveValue (name);
703 public void DeleteKey (RegistryKey rkey, string keyname, bool throw_if_missing)
705 KeyHandler self = KeyHandler.Lookup (rkey, true);
707 // key is marked for deletion
708 if (!throw_if_missing)
710 throw new ArgumentException ("the given value does not exist");
713 string dir = Path.Combine (self.Dir, ToUnix (keyname));
715 if (Directory.Exists (dir)){
716 Directory.Delete (dir, true);
717 KeyHandler.Drop (dir);
718 } else if (throw_if_missing)
719 throw new ArgumentException ("the given value does not exist");
722 public string [] GetSubKeyNames (RegistryKey rkey)
724 KeyHandler self = KeyHandler.Lookup (rkey, true);
725 DirectoryInfo selfDir = new DirectoryInfo (self.Dir);
726 DirectoryInfo[] subDirs = selfDir.GetDirectories ();
727 string[] subKeyNames = new string[subDirs.Length];
728 for (int i = 0; i < subDirs.Length; i++) {
729 DirectoryInfo subDir = subDirs[i];
730 subKeyNames[i] = subDir.Name;
735 public string [] GetValueNames (RegistryKey rkey)
737 KeyHandler self = KeyHandler.Lookup (rkey, true);
739 throw RegistryKey.CreateMarkedForDeletionException ();
740 return self.GetValueNames ();
743 public string ToString (RegistryKey rkey)
748 private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable)
750 KeyHandler self = KeyHandler.Lookup (rkey, true);
752 throw RegistryKey.CreateMarkedForDeletionException ();
753 return self.Ensure (rkey, ToUnix (keyname), writable);
756 public RegistryValueKind GetValueKind (RegistryKey rkey, string name)
758 KeyHandler self = KeyHandler.Lookup (rkey, true);
760 return self.GetValueKind (name);
762 // key was removed since it was opened or it does not exist.
763 return RegistryValueKind.Unknown;