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
;