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;
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 object GetValue (string name, RegistryValueOptions options)
313 if (IsMarkedForDeletion)
318 object value = values [name];
319 ExpandString exp = value as ExpandString;
322 if ((options & RegistryValueOptions.DoNotExpandEnvironmentNames) == 0)
323 return exp.Expand ();
325 return exp.ToString ();
328 public void SetValue (string name, object value)
330 AssertNotMarkedForDeletion ();
335 // immediately convert non-native registry values to string to avoid
336 // returning it unmodified in calls to UnixRegistryApi.GetValue
337 if (value is int || value is string || value is byte[] || value is string[])
338 values[name] = value;
340 values[name] = value.ToString ();
344 public string [] GetValueNames ()
346 AssertNotMarkedForDeletion ();
348 ICollection keys = values.Keys;
350 string [] vals = new string [keys.Count];
351 keys.CopyTo (vals, 0);
357 // This version has to do argument validation based on the valueKind
359 public void SetValue (string name, object value, RegistryValueKind valueKind)
367 case RegistryValueKind.String:
368 if (value is string){
369 values [name] = value;
373 case RegistryValueKind.ExpandString:
374 if (value is string){
375 values [name] = new ExpandString ((string)value);
380 case RegistryValueKind.Binary:
381 if (value is byte []){
382 values [name] = value;
387 case RegistryValueKind.DWord:
389 (((long) value) < Int32.MaxValue) &&
390 (((long) value) > Int32.MinValue)){
391 values [name] = (int) ((long)value);
395 values [name] = value;
400 case RegistryValueKind.MultiString:
401 if (value is string []){
402 values [name] = value;
407 case RegistryValueKind.QWord:
409 values [name] = (long) ((int) value);
413 values [name] = value;
418 throw new ArgumentException ("unknown value", "valueKind");
420 throw new ArgumentException ("Value could not be converted to specified type", "valueKind");
426 lock (typeof (KeyHandler)){
430 new Timer (DirtyTimeout, null, 3000, Timeout.Infinite);
434 public void DirtyTimeout (object state)
441 lock (typeof (KeyHandler)) {
449 public bool ValueExists (string name)
454 return values.Contains (name);
457 public int ValueCount {
459 return values.Keys.Count;
463 public bool IsMarkedForDeletion {
465 return !dir_to_handler.Contains (Dir);
469 public void RemoveValue (string name)
471 AssertNotMarkedForDeletion ();
473 values.Remove (name);
484 if (IsMarkedForDeletion)
487 if (!File.Exists (file) && values.Count == 0)
490 SecurityElement se = new SecurityElement ("values");
492 // With SecurityElement.Text = value, and SecurityElement.AddAttribute(key, value)
493 // the values must be escaped prior to being assigned.
494 foreach (DictionaryEntry de in values){
495 object val = de.Value;
496 SecurityElement value = new SecurityElement ("value");
497 value.AddAttribute ("name", SecurityElement.Escape ((string) de.Key));
500 value.AddAttribute ("type", "string");
501 value.Text = SecurityElement.Escape ((string) val);
502 } else if (val is int){
503 value.AddAttribute ("type", "int");
504 value.Text = val.ToString ();
505 } else if (val is long) {
506 value.AddAttribute ("type", "qword");
507 value.Text = val.ToString ();
508 } else if (val is byte []){
509 value.AddAttribute ("type", "bytearray");
510 value.Text = Convert.ToBase64String ((byte[]) val);
511 } else if (val is ExpandString){
512 value.AddAttribute ("type", "expand");
513 value.Text = SecurityElement.Escape (val.ToString ());
514 } else if (val is string []){
515 value.AddAttribute ("type", "string-array");
517 foreach (string ss in (string[]) val){
518 SecurityElement str = new SecurityElement ("string");
519 str.Text = SecurityElement.Escape (ss);
520 value.AddChild (str);
526 using (FileStream fs = File.Create (file)){
527 StreamWriter sw = new StreamWriter (fs);
529 sw.Write (se.ToString ());
534 private void AssertNotMarkedForDeletion ()
536 if (IsMarkedForDeletion)
537 throw RegistryKey.CreateMarkedForDeletionException ();
540 private static string UserStore {
542 return Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal),
547 private static string MachineStore {
551 s = Environment.GetEnvironmentVariable ("MONO_REGISTRY_PATH");
554 s = Environment.GetMachineConfigPath ();
555 int p = s.IndexOf ("machine.config");
556 return Path.Combine (Path.Combine (s.Substring (0, p-1), ".."), "registry");
561 internal class UnixRegistryApi : IRegistryApi {
563 static string ToUnix (string keyname)
565 if (keyname.IndexOf ('\\') != -1)
566 keyname = keyname.Replace ('\\', '/');
567 return keyname.ToLower ();
570 static bool IsWellKnownKey (string parentKeyName, string keyname)
572 // FIXME: Add more keys if needed
573 if (parentKeyName == Registry.CurrentUser.Name ||
574 parentKeyName == Registry.LocalMachine.Name)
575 return (0 == String.Compare ("software", keyname, true, CultureInfo.InvariantCulture));
580 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname)
582 return CreateSubKey (rkey, keyname, true);
585 public RegistryKey OpenRemoteBaseKey (RegistryHive hKey, string machineName)
587 throw new NotImplementedException ();
590 public RegistryKey OpenSubKey (RegistryKey rkey, string keyname, bool writable)
592 KeyHandler self = KeyHandler.Lookup (rkey, true);
594 // return null if parent is marked for deletion
598 RegistryKey result = self.Probe (rkey, ToUnix (keyname), writable);
599 if (result == null && IsWellKnownKey (rkey.Name, keyname)) {
600 // create the subkey even if its parent was opened read-only
601 result = CreateSubKey (rkey, keyname, writable);
607 public void Flush (RegistryKey rkey)
609 KeyHandler self = KeyHandler.Lookup (rkey, false);
611 // we do not need to flush changes as key is marked for deletion
617 public void Close (RegistryKey rkey)
619 KeyHandler.Drop (rkey);
622 public object GetValue (RegistryKey rkey, string name, object default_value, RegistryValueOptions options)
624 KeyHandler self = KeyHandler.Lookup (rkey, true);
626 // key was removed since it was opened
627 return default_value;
630 if (self.ValueExists (name))
631 return self.GetValue (name, options);
632 return default_value;
635 public void SetValue (RegistryKey rkey, string name, object value)
637 KeyHandler self = KeyHandler.Lookup (rkey, true);
639 throw RegistryKey.CreateMarkedForDeletionException ();
640 self.SetValue (name, value);
644 public void SetValue (RegistryKey rkey, string name, object value, RegistryValueKind valueKind)
646 KeyHandler self = KeyHandler.Lookup (rkey, true);
648 throw RegistryKey.CreateMarkedForDeletionException ();
649 self.SetValue (name, value, valueKind);
653 public int SubKeyCount (RegistryKey rkey)
655 KeyHandler self = KeyHandler.Lookup (rkey, true);
657 throw RegistryKey.CreateMarkedForDeletionException ();
658 return Directory.GetDirectories (self.Dir).Length;
661 public int ValueCount (RegistryKey rkey)
663 KeyHandler self = KeyHandler.Lookup (rkey, true);
665 throw RegistryKey.CreateMarkedForDeletionException ();
666 return self.ValueCount;
669 public void DeleteValue (RegistryKey rkey, string name, bool throw_if_missing)
671 KeyHandler self = KeyHandler.Lookup (rkey, true);
673 // if key is marked for deletion, report success regardless of
678 if (throw_if_missing && !self.ValueExists (name))
679 throw new ArgumentException ("the given value does not exist");
681 self.RemoveValue (name);
684 public void DeleteKey (RegistryKey rkey, string keyname, bool throw_if_missing)
686 KeyHandler self = KeyHandler.Lookup (rkey, true);
688 // key is marked for deletion
689 if (!throw_if_missing)
691 throw new ArgumentException ("the given value does not exist");
694 string dir = Path.Combine (self.Dir, ToUnix (keyname));
696 if (Directory.Exists (dir)){
697 Directory.Delete (dir, true);
698 KeyHandler.Drop (dir);
699 } else if (throw_if_missing)
700 throw new ArgumentException ("the given value does not exist");
703 public string [] GetSubKeyNames (RegistryKey rkey)
705 KeyHandler self = KeyHandler.Lookup (rkey, true);
706 DirectoryInfo selfDir = new DirectoryInfo (self.Dir);
707 DirectoryInfo[] subDirs = selfDir.GetDirectories ();
708 string[] subKeyNames = new string[subDirs.Length];
709 for (int i = 0; i < subDirs.Length; i++) {
710 DirectoryInfo subDir = subDirs[i];
711 subKeyNames[i] = subDir.Name;
716 public string [] GetValueNames (RegistryKey rkey)
718 KeyHandler self = KeyHandler.Lookup (rkey, true);
720 throw RegistryKey.CreateMarkedForDeletionException ();
721 return self.GetValueNames ();
724 public string ToString (RegistryKey rkey)
729 private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable)
731 KeyHandler self = KeyHandler.Lookup (rkey, true);
733 throw RegistryKey.CreateMarkedForDeletionException ();
734 return self.Ensure (rkey, ToUnix (keyname), writable);