move FrameworkName from corlib to System
[mcs.git] / class / corlib / Microsoft.Win32 / UnixRegistryApi.cs
blob3b61b156a187ab2cc600340fa97c293bff2286d1
1 //
2 // Microsoft.Win32/UnixRegistryApi.cs
3 //
4 // Authors:
5 // Miguel de Icaza (miguel@gnome.org)
6 // Gert Driesen (drieseng@users.sourceforge.net)
7 //
8 // (C) 2005, 2006 Novell, Inc (http://www.novell.com)
9 //
10 // MISSING:
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
16 // a Hashtable).
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:
27 //
28 // The above copyright notice and this permission notice shall be
29 // included in all copies or substantial portions of the Software.
30 //
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.
40 #if !NET_2_1
42 using System;
43 using System.Collections;
44 using System.Globalization;
45 using System.IO;
46 using System.Text;
47 using System.Runtime.InteropServices;
48 using System.Reflection;
49 using System.Security;
50 using System.Threading;
52 namespace Microsoft.Win32 {
54 class ExpandString {
55 string value;
57 public ExpandString (string s)
59 value = s;
62 public override string ToString ()
64 return value;
67 public string Expand ()
69 StringBuilder sb = new StringBuilder ();
71 for (int i = 0; i < value.Length; i++){
72 if (value [i] == '%'){
73 int j = i + 1;
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));
79 i += j;
80 break;
83 if (j == value.Length){
84 sb.Append ('%');
86 } else {
87 sb.Append (value [i]);
90 return sb.ToString ();
94 class KeyHandler
96 static Hashtable key_to_handler = new Hashtable ();
97 static Hashtable dir_to_handler = new Hashtable (
98 new CaseInsensitiveHashCodeProvider (), new CaseInsensitiveComparer ());
99 public string Dir;
101 Hashtable values;
102 string file;
103 bool dirty;
105 KeyHandler (RegistryKey rkey, string basedir)
107 if (!Directory.Exists (basedir)){
108 try {
109 Directory.CreateDirectory (basedir);
110 } catch (UnauthorizedAccessException){
111 throw new SecurityException ("No access to the given key");
114 Dir = basedir;
115 file = Path.Combine (Dir, "values.xml");
116 Load ();
119 public void Load ()
121 values = new Hashtable ();
122 if (!File.Exists (file))
123 return;
125 try {
126 using (FileStream fs = File.OpenRead (file)){
127 StreamReader r = new StreamReader (fs);
128 string xml = r.ReadToEnd ();
129 if (xml.Length == 0)
130 return;
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"){
136 LoadKey (value);
141 } catch (UnauthorizedAccessException){
142 values.Clear ();
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);
146 values.Clear ();
150 void LoadKey (SecurityElement se)
152 Hashtable h = se.Attributes;
153 try {
154 string name = (string) h ["name"];
155 if (name == null)
156 return;
157 string type = (string) h ["type"];
158 if (type == null)
159 return;
161 switch (type){
162 case "int":
163 values [name] = Int32.Parse (se.Text);
164 break;
165 case "bytearray":
166 values [name] = Convert.FromBase64String (se.Text);
167 break;
168 case "string":
169 values [name] = se.Text == null ? String.Empty : se.Text;
170 break;
171 case "expand":
172 values [name] = new ExpandString (se.Text);
173 break;
174 case "qword":
175 values [name] = Int64.Parse (se.Text);
176 break;
177 case "string-array":
178 ArrayList sa = new ArrayList ();
179 if (se.Children != null){
180 foreach (SecurityElement stre in se.Children){
181 sa.Add (stre.Text);
184 values [name] = sa.ToArray (typeof (string));
185 break;
187 } catch {
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];
197 if (kh == null)
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;
202 return rk;
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];
213 if (kh != null) {
214 rk = new RegistryKey (kh, CombineName (rkey,
215 extra), writable);
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),
220 writable);
221 dir_to_handler [f] = kh;
222 key_to_handler [rk] = kh;
224 return rk;
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];
240 if (k != null)
241 return k;
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)
246 return null;
248 RegistryHive x = (RegistryHive) rkey.Hive;
249 switch (x){
250 case RegistryHive.CurrentUser:
251 string userDir = Path.Combine (UserStore, x.ToString ());
252 k = new KeyHandler (rkey, userDir);
253 dir_to_handler [userDir] = k;
254 break;
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;
264 break;
265 default:
266 throw new Exception ("Unknown RegistryHive");
268 key_to_handler [rkey] = k;
269 return k;
273 public static void Drop (RegistryKey rkey)
275 lock (typeof (KeyHandler)) {
276 KeyHandler k = (KeyHandler) key_to_handler [rkey];
277 if (k == null)
278 return;
279 key_to_handler.Remove (rkey);
281 // remove cached KeyHandler if no other keys reference it
282 int refCount = 0;
283 foreach (DictionaryEntry de in key_to_handler)
284 if (de.Value == k)
285 refCount++;
286 if (refCount == 0)
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];
295 if (kh == null)
296 return;
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)
303 if (de.Value == kh)
304 keys.Add (de.Key);
306 foreach (object key in keys)
307 key_to_handler.Remove (key);
311 public RegistryValueKind GetValueKind (string name)
313 if (name == null)
314 return RegistryValueKind.Unknown;
315 object value = values [name];
316 if (value == null)
317 return RegistryValueKind.Unknown;
319 if (value is int)
320 return RegistryValueKind.DWord;
321 if (value is string [])
322 return RegistryValueKind.MultiString;
323 if (value is long)
324 return RegistryValueKind.QWord;
325 if (value is byte [])
326 return RegistryValueKind.Binary;
327 if (value is string)
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)
337 return null;
339 if (name == null)
340 name = string.Empty;
341 object value = values [name];
342 ExpandString exp = value as ExpandString;
343 if (exp == null)
344 return value;
345 if ((options & RegistryValueOptions.DoNotExpandEnvironmentNames) == 0)
346 return exp.Expand ();
348 return exp.ToString ();
351 public void SetValue (string name, object value)
353 AssertNotMarkedForDeletion ();
355 if (name == null)
356 name = string.Empty;
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;
362 else
363 values[name] = value.ToString ();
364 SetDirty ();
367 public string [] GetValueNames ()
369 AssertNotMarkedForDeletion ();
371 ICollection keys = values.Keys;
373 string [] vals = new string [keys.Count];
374 keys.CopyTo (vals, 0);
375 return vals;
379 // This version has to do argument validation based on the valueKind
381 public void SetValue (string name, object value, RegistryValueKind valueKind)
383 SetDirty ();
385 if (name == null)
386 name = string.Empty;
388 switch (valueKind){
389 case RegistryValueKind.String:
390 if (value is string){
391 values [name] = value;
392 return;
394 break;
395 case RegistryValueKind.ExpandString:
396 if (value is string){
397 values [name] = new ExpandString ((string)value);
398 return;
400 break;
402 case RegistryValueKind.Binary:
403 if (value is byte []){
404 values [name] = value;
405 return;
407 break;
409 case RegistryValueKind.DWord:
410 if (value is long &&
411 (((long) value) < Int32.MaxValue) &&
412 (((long) value) > Int32.MinValue)){
413 values [name] = (int) ((long)value);
414 return;
416 if (value is int){
417 values [name] = value;
418 return;
420 break;
422 case RegistryValueKind.MultiString:
423 if (value is string []){
424 values [name] = value;
425 return;
427 break;
429 case RegistryValueKind.QWord:
430 if (value is int){
431 values [name] = (long) ((int) value);
432 return;
434 if (value is long){
435 values [name] = value;
436 return;
438 break;
439 default:
440 throw new ArgumentException ("unknown value", "valueKind");
442 throw new ArgumentException ("Value could not be converted to specified type", "valueKind");
445 void SetDirty ()
447 lock (typeof (KeyHandler)){
448 if (dirty)
449 return;
450 dirty = true;
451 new Timer (DirtyTimeout, null, 3000, Timeout.Infinite);
455 public void DirtyTimeout (object state)
457 Flush ();
460 public void Flush ()
462 lock (typeof (KeyHandler)) {
463 if (dirty) {
464 Save ();
465 dirty = false;
470 public bool ValueExists (string name)
472 if (name == null)
473 name = string.Empty;
475 return values.Contains (name);
478 public int ValueCount {
479 get {
480 return values.Keys.Count;
484 public bool IsMarkedForDeletion {
485 get {
486 return !dir_to_handler.Contains (Dir);
490 public void RemoveValue (string name)
492 AssertNotMarkedForDeletion ();
494 values.Remove (name);
495 SetDirty ();
498 ~KeyHandler ()
500 Flush ();
503 void Save ()
505 if (IsMarkedForDeletion)
506 return;
508 if (!File.Exists (file) && values.Count == 0)
509 return;
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));
520 if (val is string){
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);
544 se.AddChild (value);
547 using (FileStream fs = File.Create (file)){
548 StreamWriter sw = new StreamWriter (fs);
550 sw.Write (se.ToString ());
551 sw.Flush ();
555 private void AssertNotMarkedForDeletion ()
557 if (IsMarkedForDeletion)
558 throw RegistryKey.CreateMarkedForDeletionException ();
561 private static string UserStore {
562 get {
563 return Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal),
564 ".mono/registry");
568 private static string MachineStore {
569 get {
570 string s;
572 s = Environment.GetEnvironmentVariable ("MONO_REGISTRY_PATH");
573 if (s != null)
574 return s;
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));
598 return false;
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);
614 if (self == null) {
615 // return null if parent is marked for deletion
616 return null;
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);
625 return result;
628 public void Flush (RegistryKey rkey)
630 KeyHandler self = KeyHandler.Lookup (rkey, false);
631 if (self == null) {
632 // we do not need to flush changes as key is marked for deletion
633 return;
635 self.Flush ();
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);
646 if (self == null) {
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);
659 if (self == null)
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);
667 if (self == null)
668 throw RegistryKey.CreateMarkedForDeletionException ();
669 self.SetValue (name, value, valueKind);
672 public int SubKeyCount (RegistryKey rkey)
674 KeyHandler self = KeyHandler.Lookup (rkey, true);
675 if (self == null)
676 throw RegistryKey.CreateMarkedForDeletionException ();
677 return Directory.GetDirectories (self.Dir).Length;
680 public int ValueCount (RegistryKey rkey)
682 KeyHandler self = KeyHandler.Lookup (rkey, true);
683 if (self == null)
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);
691 if (self == null) {
692 // if key is marked for deletion, report success regardless of
693 // throw_if_missing
694 return;
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);
706 if (self == null) {
707 // key is marked for deletion
708 if (!throw_if_missing)
709 return;
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;
732 return subKeyNames;
735 public string [] GetValueNames (RegistryKey rkey)
737 KeyHandler self = KeyHandler.Lookup (rkey, true);
738 if (self == null)
739 throw RegistryKey.CreateMarkedForDeletionException ();
740 return self.GetValueNames ();
743 public string ToString (RegistryKey rkey)
745 return rkey.Name;
748 private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable)
750 KeyHandler self = KeyHandler.Lookup (rkey, true);
751 if (self == null)
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);
759 if (self != null)
760 return self.GetValueKind (name);
762 // key was removed since it was opened or it does not exist.
763 return RegistryValueKind.Unknown;
769 #endif // NET_2_1