Implement 4.0 RegistryKey.Handle property.
[mono.git] / mcs / class / corlib / Microsoft.Win32 / UnixRegistryApi.cs
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%
14 //
15 //   We should use an ordered collection for storing the values (instead of
16 //   a Hashtable).
17 //
18 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
19 //
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.
38 //
39
40 #if !NET_2_1
41
42 using System;
43 using System.Collections;
44 using System.Collections.Generic;
45 using System.Globalization;
46 using System.IO;
47 using System.Text;
48 using System.Runtime.InteropServices;
49 using System.Reflection;
50 using System.Security;
51 using System.Threading;
52 using Microsoft.Win32.SafeHandles;
53
54 namespace Microsoft.Win32 {
55
56         class ExpandString {
57                 string value;
58                 
59                 public ExpandString (string s)
60                 {
61                         value = s;
62                 }
63
64                 public override string ToString ()
65                 {
66                         return value;
67                 }
68
69                 public string Expand ()
70                 {
71                         StringBuilder sb = new StringBuilder ();
72
73                         for (int i = 0; i < value.Length; i++){
74                                 if (value [i] == '%'){
75                                         int j = i + 1;
76                                         for (; j < value.Length; j++){
77                                                 if (value [j] == '%'){
78                                                         string key = value.Substring (i + 1, j - i - 1);
79
80                                                         sb.Append (Environment.GetEnvironmentVariable (key));
81                                                         i += j;
82                                                         break;
83                                                 }
84                                         }
85                                         if (j == value.Length){
86                                                 sb.Append ('%');
87                                         }
88                                 } else {
89                                         sb.Append (value [i]);
90                                 }
91                         }
92                         return sb.ToString ();
93                 }
94         }
95
96         class KeyHandler
97         {
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";
102
103                 public string Dir;
104                 string ActualDir; // Lets keep this one private.
105                 public bool IsVolatile;
106
107                 Hashtable values;
108                 string file;
109                 bool dirty;
110
111                 static KeyHandler ()
112                 {
113                         CleanVolatileKeys ();
114                 }
115
116                 KeyHandler (RegistryKey rkey, string basedir) : this (rkey, basedir, false)
117                 {
118                 }
119
120                 KeyHandler (RegistryKey rkey, string basedir, bool is_volatile)
121                 {
122                         // Force ourselved to reuse the key, if any.
123                         string volatile_basedir = GetVolatileDir (basedir);
124                         string actual_basedir = basedir;
125
126                         if (Directory.Exists (basedir))
127                                 is_volatile = false;
128                         else if (Directory.Exists (volatile_basedir)) {
129                                 actual_basedir = volatile_basedir;
130                                 is_volatile = true;
131                         } else if (is_volatile)
132                                 actual_basedir = volatile_basedir;
133
134                         if (!Directory.Exists (actual_basedir)) {
135                                 try {
136                                         Directory.CreateDirectory (actual_basedir);
137                                 } catch (UnauthorizedAccessException){
138                                         throw new SecurityException ("No access to the given key");
139                                 }
140                         }
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");
145                         Load ();
146                 }
147
148                 public void Load ()
149                 {
150                         values = new Hashtable ();
151                         if (!File.Exists (file))
152                                 return;
153                         
154                         try {
155                                 using (FileStream fs = File.OpenRead (file)){
156                                         StreamReader r = new StreamReader (fs);
157                                         string xml = r.ReadToEnd ();
158                                         if (xml.Length == 0)
159                                                 return;
160                                         
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"){
165                                                                 LoadKey (value);
166                                                         }
167                                                 }
168                                         }
169                                 }
170                         } catch (UnauthorizedAccessException){
171                                 values.Clear ();
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);
175                                 values.Clear ();
176                         }
177                 }
178
179                 void LoadKey (SecurityElement se)
180                 {
181                         Hashtable h = se.Attributes;
182                         try {
183                                 string name = (string) h ["name"];
184                                 if (name == null)
185                                         return;
186                                 string type = (string) h ["type"];
187                                 if (type == null)
188                                         return;
189                                 
190                                 switch (type){
191                                 case "int":
192                                         values [name] = Int32.Parse (se.Text);
193                                         break;
194                                 case "bytearray":
195                                         values [name] = Convert.FromBase64String (se.Text);
196                                         break;
197                                 case "string":
198                                         values [name] = se.Text == null ? String.Empty : se.Text;
199                                         break;
200                                 case "expand":
201                                         values [name] = new ExpandString (se.Text);
202                                         break;
203                                 case "qword":
204                                         values [name] = Int64.Parse (se.Text);
205                                         break;
206                                 case "string-array":
207                                         ArrayList sa = new ArrayList ();
208                                         if (se.Children != null){
209                                                 foreach (SecurityElement stre in se.Children){
210                                                         sa.Add (stre.Text);
211                                                 }
212                                         }
213                                         values [name] = sa.ToArray (typeof (string));
214                                         break;
215                                 }
216                         } catch {
217                                 // We ignore individual errors in the file.
218                         }
219                 }
220
221                 public RegistryKey Ensure (RegistryKey rkey, string extra, bool writable)
222                 {
223                         return Ensure (rkey, extra, writable, false);
224                 }
225
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)
228                 {
229                         lock (typeof (KeyHandler)){
230                                 string f = Path.Combine (Dir, extra);
231                                 KeyHandler kh = (KeyHandler) dir_to_handler [f];
232                                 if (kh == null)
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;
237                                 return rk;
238                         }
239                 }
240
241                 public RegistryKey Probe (RegistryKey rkey, string extra, bool writable)
242                 {
243                         RegistryKey rk = null;
244
245                         lock (typeof (KeyHandler)){
246                                 string f = Path.Combine (Dir, extra);
247                                 KeyHandler kh = (KeyHandler) dir_to_handler [f];
248                                 if (kh != null) {
249                                         rk = new RegistryKey (kh, CombineName (rkey,
250                                                 extra), writable);
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),
255                                                 writable);
256                                         dir_to_handler [f] = kh;
257                                         key_to_handler [rk] = kh;
258                                 }
259                                 return rk;
260                         }
261                 }
262
263                 static string CombineName (RegistryKey rkey, string extra)
264                 {
265                         if (extra.IndexOf ('/') != -1)
266                                 extra = extra.Replace ('/', '\\');
267                         
268                         return String.Concat (rkey.Name, "\\", extra);
269                 }
270
271                 static long GetSystemBootTime ()
272                 {
273                         if (!File.Exists ("/proc/stat"))
274                                 return -1;
275
276                         string btime = null;
277                         string line;
278
279                         try {
280                                 using (StreamReader stat_file = new StreamReader ("/proc/stat", Encoding.ASCII)) {
281                                         while ((line = stat_file.ReadLine ()) != null)
282                                                 if (line.StartsWith ("btime")) {
283                                                         btime = line;
284                                                         break;
285                                                 }
286                                 }
287                         } catch (Exception e) {
288                                 Console.Error.WriteLine ("While reading system info {0}", e);
289                         }
290
291                         if (btime == null)
292                                 return -1;
293
294                         int space = btime.IndexOf (' ');
295                         long res;
296                         if (!Int64.TryParse (btime.Substring (space, btime.Length - space), out res))
297                                 return -1;
298
299                         return res;
300                 }
301
302                 // The registered boot time it's a simple line containing the last system btime.
303                 static long GetRegisteredBootTime (string path)
304                 {
305                         if (!File.Exists (path))
306                                 return -1;
307
308                         string line = null;
309                         try {
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);
314                         }
315
316                         if (line == null)
317                                 return -1;
318
319                         long res;
320                         if (!Int64.TryParse (line, out res))
321                                 return -1;
322
323                         return res;
324                 }
325
326                 static void SaveRegisteredBootTime (string path, long btime)
327                 {
328                         try {
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);
333                         }
334                 }
335                         
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 ()
339                 {
340                         long system_btime = GetSystemBootTime ();
341
342                         string [] roots = new string [] {
343                                 UserStore,
344                                 MachineStore
345                         };
346
347                         foreach (string root in roots) {
348                                 if (!Directory.Exists (root))
349                                         continue;
350
351                                 string btime_file = Path.Combine (root, "last-btime");
352                                 string volatile_dir = Path.Combine (root, VolatileDirectoryName);
353
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);
358                                 }
359
360                                 SaveRegisteredBootTime (btime_file, system_btime);
361                         }
362                 }
363         
364                 public static bool VolatileKeyExists (string dir)
365                 {
366                         lock (typeof (KeyHandler)) {
367                                 KeyHandler kh = (KeyHandler) dir_to_handler [dir];
368                                 if (kh != null)
369                                         return kh.IsVolatile;
370                         }
371
372                         if (Directory.Exists (dir)) // Non-volatile key exists.
373                                 return false;
374
375                         return Directory.Exists (GetVolatileDir (dir));
376                 }
377
378                 public static string GetVolatileDir (string dir)
379                 {
380                         string root = GetRootFromDir (dir);
381                         string volatile_dir = dir.Replace (root, Path.Combine (root, VolatileDirectoryName));
382                         return volatile_dir;
383                 }
384
385                 public static KeyHandler Lookup (RegistryKey rkey, bool createNonExisting)
386                 {
387                         lock (typeof (KeyHandler)){
388                                 KeyHandler k = (KeyHandler) key_to_handler [rkey];
389                                 if (k != null)
390                                         return k;
391
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)
395                                         return null;
396
397                                 RegistryHive x = (RegistryHive) rkey.Hive;
398                                 switch (x){
399                                 case RegistryHive.CurrentUser:
400                                         string userDir = Path.Combine (UserStore, x.ToString ());
401                                         k = new KeyHandler (rkey, userDir);
402                                         dir_to_handler [userDir] = k;
403                                         break;
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;
413                                         break;
414                                 default:
415                                         throw new Exception ("Unknown RegistryHive");
416                                 }
417                                 key_to_handler [rkey] = k;
418                                 return k;
419                         }
420                 }
421
422                 static string GetRootFromDir (string dir)
423                 {
424                         if (dir.IndexOf (UserStore) > -1)
425                                 return UserStore;
426                         else if (dir.IndexOf (MachineStore) > -1)
427                                 return MachineStore;
428
429                         throw new Exception ("Could not get root for dir " + dir);
430                 }
431
432                 public static void Drop (RegistryKey rkey)
433                 {
434                         lock (typeof (KeyHandler)) {
435                                 KeyHandler k = (KeyHandler) key_to_handler [rkey];
436                                 if (k == null)
437                                         return;
438                                 key_to_handler.Remove (rkey);
439
440                                 // remove cached KeyHandler if no other keys reference it
441                                 int refCount = 0;
442                                 foreach (DictionaryEntry de in key_to_handler)
443                                         if (de.Value == k)
444                                                 refCount++;
445                                 if (refCount == 0)
446                                         dir_to_handler.Remove (k.Dir);
447                         }
448                 }
449
450                 public static void Drop (string dir)
451                 {
452                         lock (typeof (KeyHandler)) {
453                                 KeyHandler kh = (KeyHandler) dir_to_handler [dir];
454                                 if (kh == null)
455                                         return;
456
457                                 dir_to_handler.Remove (dir);
458
459                                 // remove (other) references to keyhandler
460                                 ArrayList keys = new ArrayList ();
461                                 foreach (DictionaryEntry de in key_to_handler)
462                                         if (de.Value == kh)
463                                                 keys.Add (de.Key);
464
465                                 foreach (object key in keys)
466                                         key_to_handler.Remove (key);
467                         }
468                 }
469
470                 public static bool Delete (string dir)
471                 {
472                         if (!Directory.Exists (dir)) {
473                                 string volatile_dir = GetVolatileDir (dir);
474                                 if (!Directory.Exists (volatile_dir))
475                                         return false;
476
477                                 dir = volatile_dir;
478                         }
479
480                         Directory.Delete (dir, true);
481                         Drop (dir);
482                         return true;
483                 }
484
485                 public RegistryValueKind GetValueKind (string name)
486                 {
487                         if (name == null)
488                                 return RegistryValueKind.Unknown;
489                         object value = values [name];
490                         if (value == null)
491                                 return RegistryValueKind.Unknown;
492
493                         if (value is int)
494                                 return RegistryValueKind.DWord;
495                         if (value is string [])
496                                 return RegistryValueKind.MultiString;
497                         if (value is long)
498                                 return RegistryValueKind.QWord;
499                         if (value is byte [])
500                                 return RegistryValueKind.Binary;
501                         if (value is string)
502                                 return RegistryValueKind.String;
503                         if (value is ExpandString)
504                                 return RegistryValueKind.ExpandString;
505                         return RegistryValueKind.Unknown;
506                 }
507                 
508                 public object GetValue (string name, RegistryValueOptions options)
509                 {
510                         if (IsMarkedForDeletion)
511                                 return null;
512
513                         if (name == null)
514                                 name = string.Empty;
515                         object value = values [name];
516                         ExpandString exp = value as ExpandString;
517                         if (exp == null)
518                                 return value;
519                         if ((options & RegistryValueOptions.DoNotExpandEnvironmentNames) == 0)
520                                 return exp.Expand ();
521
522                         return exp.ToString ();
523                 }
524
525                 public void SetValue (string name, object value)
526                 {
527                         AssertNotMarkedForDeletion ();
528
529                         if (name == null)
530                                 name = string.Empty;
531
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;
536                         else
537                                 values[name] = value.ToString ();
538                         SetDirty ();
539                 }
540
541                 public string [] GetValueNames ()
542                 {
543                         AssertNotMarkedForDeletion ();
544
545                         ICollection keys = values.Keys;
546
547                         string [] vals = new string [keys.Count];
548                         keys.CopyTo (vals, 0);
549                         return vals;
550                 }
551
552                 public int GetSubKeyCount ()
553                 {
554                         return GetSubKeyNames ().Length;
555                 }
556
557                 public string [] GetSubKeyNames ()
558                 {
559                         DirectoryInfo selfDir = new DirectoryInfo (ActualDir);
560                         DirectoryInfo[] subDirs = selfDir.GetDirectories ();
561                         string[] subKeyNames;
562
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;
570                                 }
571                                 return subKeyNames;
572                         }
573
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> ();
578
579                         foreach (DirectoryInfo dir in subDirs)
580                                 dirs [dir.Name] = dir.Name;
581                         foreach (DirectoryInfo volDir in volatileSubDirs)
582                                 dirs [volDir.Name] = volDir.Name;
583
584                         subKeyNames = new string [dirs.Count];
585                         int j = 0;
586                         foreach (KeyValuePair<string,string> entry in dirs)
587                                 subKeyNames[j++] = entry.Value;
588
589                         return subKeyNames;
590                 }
591
592                 //
593                 // This version has to do argument validation based on the valueKind
594                 //
595                 public void SetValue (string name, object value, RegistryValueKind valueKind)
596                 {
597                         SetDirty ();
598
599                         if (name == null)
600                                 name = string.Empty;
601
602                         switch (valueKind){
603                         case RegistryValueKind.String:
604                                 if (value is string){
605                                         values [name] = value;
606                                         return;
607                                 }
608                                 break;
609                         case RegistryValueKind.ExpandString:
610                                 if (value is string){
611                                         values [name] = new ExpandString ((string)value);
612                                         return;
613                                 }
614                                 break;
615                                 
616                         case RegistryValueKind.Binary:
617                                 if (value is byte []){
618                                         values [name] = value;
619                                         return;
620                                 }
621                                 break;
622                                 
623                         case RegistryValueKind.DWord:
624                                 if (value is long &&
625                                     (((long) value) < Int32.MaxValue) &&
626                                     (((long) value) > Int32.MinValue)){
627                                         values [name] = (int) ((long)value);
628                                         return;
629                                 }
630                                 if (value is int){
631                                         values [name] = value;
632                                         return;
633                                 }
634                                 break;
635                                 
636                         case RegistryValueKind.MultiString:
637                                 if (value is string []){
638                                         values [name] = value;
639                                         return;
640                                 }
641                                 break;
642                                 
643                         case RegistryValueKind.QWord:
644                                 if (value is int){
645                                         values [name] = (long) ((int) value);
646                                         return;
647                                 }
648                                 if (value is long){
649                                         values [name] = value;
650                                         return;
651                                 }
652                                 break;
653                         default:
654                                 throw new ArgumentException ("unknown value", "valueKind");
655                         }
656                         throw new ArgumentException ("Value could not be converted to specified type", "valueKind");
657                 }
658
659                 void SetDirty ()
660                 {
661                         lock (typeof (KeyHandler)){
662                                 if (dirty)
663                                         return;
664                                 dirty = true;
665                                 new Timer (DirtyTimeout, null, 3000, Timeout.Infinite);
666                         }
667                 }
668
669                 public void DirtyTimeout (object state)
670                 {
671                         Flush ();
672                 }
673
674                 public void Flush ()
675                 {
676                         lock (typeof (KeyHandler)) {
677                                 if (dirty) {
678                                         Save ();
679                                         dirty = false;
680                                 }
681                         }
682                 }
683
684                 public bool ValueExists (string name)
685                 {
686                         if (name == null)
687                                 name = string.Empty;
688
689                         return values.Contains (name);
690                 }
691
692                 public int ValueCount {
693                         get {
694                                 return values.Keys.Count;
695                         }
696                 }
697
698                 public bool IsMarkedForDeletion {
699                         get {
700                                 return !dir_to_handler.Contains (Dir);
701                         }
702                 }
703
704                 public void RemoveValue (string name)
705                 {
706                         AssertNotMarkedForDeletion ();
707
708                         values.Remove (name);
709                         SetDirty ();
710                 }
711
712                 ~KeyHandler ()
713                 {
714                         Flush ();
715                 }
716                 
717                 void Save ()
718                 {
719                         if (IsMarkedForDeletion)
720                                 return;
721
722                         if (!File.Exists (file) && values.Count == 0)
723                                 return;
724
725                         SecurityElement se = new SecurityElement ("values");
726                         
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));
733                                 
734                                 if (val is string){
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");
751
752                                         foreach (string ss in (string[]) val){
753                                                 SecurityElement str = new SecurityElement ("string");
754                                                 str.Text = SecurityElement.Escape (ss); 
755                                                 value.AddChild (str);
756                                         }
757                                 }
758                                 se.AddChild (value);
759                         }
760
761                         using (FileStream fs = File.Create (file)){
762                                 StreamWriter sw = new StreamWriter (fs);
763
764                                 sw.Write (se.ToString ());
765                                 sw.Flush ();
766                         }
767                 }
768
769                 private void AssertNotMarkedForDeletion ()
770                 {
771                         if (IsMarkedForDeletion)
772                                 throw RegistryKey.CreateMarkedForDeletionException ();
773                 }
774
775                 static string user_store;
776                 static string machine_store;
777
778                 private static string UserStore {
779                         get {
780                                 if (user_store == null)
781                                         user_store = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal),
782                                         ".mono/registry");
783
784                                 return user_store;
785                         }
786                 }
787
788                 private static string MachineStore {
789                         get {
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");
796                                         }
797                                 }
798
799                                 return machine_store;
800                         }
801                 }
802         }
803         
804         internal class UnixRegistryApi : IRegistryApi {
805
806                 static string ToUnix (string keyname)
807                 {
808                         if (keyname.IndexOf ('\\') != -1)
809                                 keyname = keyname.Replace ('\\', '/');
810                         return keyname.ToLower ();
811                 }
812
813                 static bool IsWellKnownKey (string parentKeyName, string keyname)
814                 {
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));
819
820                         return false;
821                 }
822
823                 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname)
824                 {
825                         return CreateSubKey (rkey, keyname, true);
826                 }
827
828 #if NET_4_0
829                 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname, RegistryOptions options)
830                 {
831                         return CreateSubKey (rkey, keyname, true, options == RegistryOptions.Volatile);
832                 }
833 #endif
834
835                 public RegistryKey OpenRemoteBaseKey (RegistryHive hKey, string machineName)
836                 {
837                         throw new NotImplementedException ();
838                 }
839
840                 public RegistryKey OpenSubKey (RegistryKey rkey, string keyname, bool writable)
841                 {
842                         KeyHandler self = KeyHandler.Lookup (rkey, true);
843                         if (self == null) {
844                                 // return null if parent is marked for deletion
845                                 return null;
846                         }
847
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);
852                         }
853
854                         return result;
855                 }
856
857 #if NET_4_0
858                 public RegistryKey FromHandle (SafeRegistryHandle handle)
859                 {
860                         throw new NotImplementedException ();
861                 }
862 #endif
863                 
864                 public void Flush (RegistryKey rkey)
865                 {
866                         KeyHandler self = KeyHandler.Lookup (rkey, false);
867                         if (self == null) {
868                                 // we do not need to flush changes as key is marked for deletion
869                                 return;
870                         }
871                         self.Flush ();
872                 }
873                 
874                 public void Close (RegistryKey rkey)
875                 {
876                         KeyHandler.Drop (rkey);
877                 }
878
879                 public object GetValue (RegistryKey rkey, string name, object default_value, RegistryValueOptions options)
880                 {
881                         KeyHandler self = KeyHandler.Lookup (rkey, true);
882                         if (self == null) {
883                                 // key was removed since it was opened
884                                 return default_value;
885                         }
886
887                         if (self.ValueExists (name))
888                                 return self.GetValue (name, options);
889                         return default_value;
890                 }
891                 
892                 public void SetValue (RegistryKey rkey, string name, object value)
893                 {
894                         KeyHandler self = KeyHandler.Lookup (rkey, true);
895                         if (self == null)
896                                 throw RegistryKey.CreateMarkedForDeletionException ();
897                         self.SetValue (name, value);
898                 }
899
900                 public void SetValue (RegistryKey rkey, string name, object value, RegistryValueKind valueKind)
901                 {
902                         KeyHandler self = KeyHandler.Lookup (rkey, true);
903                         if (self == null)
904                                 throw RegistryKey.CreateMarkedForDeletionException ();
905                         self.SetValue (name, value, valueKind);
906                 }
907
908                 public int SubKeyCount (RegistryKey rkey)
909                 {
910                         KeyHandler self = KeyHandler.Lookup (rkey, true);
911                         if (self == null)
912                                 throw RegistryKey.CreateMarkedForDeletionException ();
913                         return self.GetSubKeyCount ();
914                 }
915                 
916                 public int ValueCount (RegistryKey rkey)
917                 {
918                         KeyHandler self = KeyHandler.Lookup (rkey, true);
919                         if (self == null)
920                                 throw RegistryKey.CreateMarkedForDeletionException ();
921                         return self.ValueCount;
922                 }
923                 
924                 public void DeleteValue (RegistryKey rkey, string name, bool throw_if_missing)
925                 {
926                         KeyHandler self = KeyHandler.Lookup (rkey, true);
927                         if (self == null) {
928                                 // if key is marked for deletion, report success regardless of
929                                 // throw_if_missing
930                                 return;
931                         }
932
933                         if (throw_if_missing && !self.ValueExists (name))
934                                 throw new ArgumentException ("the given value does not exist");
935
936                         self.RemoveValue (name);
937                 }
938                 
939                 public void DeleteKey (RegistryKey rkey, string keyname, bool throw_if_missing)
940                 {
941                         KeyHandler self = KeyHandler.Lookup (rkey, true);
942                         if (self == null) {
943                                 // key is marked for deletion
944                                 if (!throw_if_missing)
945                                         return;
946                                 throw new ArgumentException ("the given value does not exist");
947                         }
948
949                         string dir = Path.Combine (self.Dir, ToUnix (keyname));
950                         
951                         if (!KeyHandler.Delete (dir) && throw_if_missing)
952                                 throw new ArgumentException ("the given value does not exist");
953                 }
954                 
955                 public string [] GetSubKeyNames (RegistryKey rkey)
956                 {
957                         KeyHandler self = KeyHandler.Lookup (rkey, true);
958                         return self.GetSubKeyNames ();
959                 }
960                 
961                 public string [] GetValueNames (RegistryKey rkey)
962                 {
963                         KeyHandler self = KeyHandler.Lookup (rkey, true);
964                         if (self == null)
965                                 throw RegistryKey.CreateMarkedForDeletionException ();
966                         return self.GetValueNames ();
967                 }
968
969                 public string ToString (RegistryKey rkey)
970                 {
971                         return rkey.Name;
972                 }
973
974                 private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable)
975                 {
976                         return CreateSubKey (rkey, keyname, writable, false);
977                 }
978
979                 private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable, bool is_volatile)
980                 {
981                         KeyHandler self = KeyHandler.Lookup (rkey, true);
982                         if (self == null)
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.");
986
987                         return self.Ensure (rkey, ToUnix (keyname), writable, is_volatile);
988                 }
989
990                 public RegistryValueKind GetValueKind (RegistryKey rkey, string name)
991                 {
992                         KeyHandler self = KeyHandler.Lookup (rkey, true);
993                         if (self != null) 
994                                 return self.GetValueKind (name);
995
996                         // key was removed since it was opened or it does not exist.
997                         return RegistryValueKind.Unknown;
998                 }
999
1000 #if NET_4_0
1001                 public IntPtr GetHandle (RegistryKey key)
1002                 {
1003                         throw new NotImplementedException ();
1004                 }
1005 #endif
1006                 
1007         }
1008 }
1009
1010 #endif // NET_2_1
1011