757dc5eb2138566f88b5fcda188556348eee4fcd
[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 RegistryKeyComparer : IEqualityComparer {
97                 public new bool Equals(object x, object y)
98                 {
99                         return RegistryKey.IsEquals ((RegistryKey) x, (RegistryKey) y);
100                         
101                 }
102
103                 public int GetHashCode(object obj)
104                 {
105                         var n = ((RegistryKey) obj).Name;
106                         if (n == null)
107                                 return 0;
108                         return n.GetHashCode ();
109                 }
110         }
111         
112         class KeyHandler
113         {
114                 static Hashtable key_to_handler = new Hashtable (new RegistryKeyComparer ());
115                 static Hashtable dir_to_handler = new Hashtable (
116                         new CaseInsensitiveHashCodeProvider (), new CaseInsensitiveComparer ());
117                 const string VolatileDirectoryName = "volatile-keys";
118
119                 public string Dir;
120                 string ActualDir; // Lets keep this one private.
121                 public bool IsVolatile;
122
123                 Hashtable values;
124                 string file;
125                 bool dirty;
126
127                 static KeyHandler ()
128                 {
129                         CleanVolatileKeys ();
130                 }
131
132                 KeyHandler (RegistryKey rkey, string basedir) : this (rkey, basedir, false)
133                 {
134                 }
135
136                 KeyHandler (RegistryKey rkey, string basedir, bool is_volatile)
137                 {
138                         // Force ourselved to reuse the key, if any.
139                         string volatile_basedir = GetVolatileDir (basedir);
140                         string actual_basedir = basedir;
141
142                         if (Directory.Exists (basedir))
143                                 is_volatile = false;
144                         else if (Directory.Exists (volatile_basedir)) {
145                                 actual_basedir = volatile_basedir;
146                                 is_volatile = true;
147                         } else if (is_volatile)
148                                 actual_basedir = volatile_basedir;
149
150                         if (!Directory.Exists (actual_basedir)) {
151                                 try {
152                                         Directory.CreateDirectory (actual_basedir);
153                                 } catch (UnauthorizedAccessException ex){
154                                         throw new SecurityException ("No access to the given key", ex);
155                                 }
156                         }
157                         Dir = basedir; // This is our identifier.
158                         ActualDir = actual_basedir; // This our actual location.
159                         IsVolatile = is_volatile;
160                         file = Path.Combine (ActualDir, "values.xml");
161                         Load ();
162                 }
163
164                 public void Load ()
165                 {
166                         values = new Hashtable ();
167                         if (!File.Exists (file))
168                                 return;
169                         
170                         try {
171                                 using (FileStream fs = File.OpenRead (file)){
172                                         StreamReader r = new StreamReader (fs);
173                                         string xml = r.ReadToEnd ();
174                                         if (xml.Length == 0)
175                                                 return;
176                                         
177                                         SecurityElement tree = SecurityElement.FromString (xml);
178                                         if (tree.Tag == "values" && tree.Children != null){
179                                                 foreach (SecurityElement value in tree.Children){
180                                                         if (value.Tag == "value"){
181                                                                 LoadKey (value);
182                                                         }
183                                                 }
184                                         }
185                                 }
186                         } catch (UnauthorizedAccessException){
187                                 values.Clear ();
188                                 throw new SecurityException ("No access to the given key");
189                         } catch (Exception e){
190                                 Console.Error.WriteLine ("While loading registry key at {0}: {1}", file, e);
191                                 values.Clear ();
192                         }
193                 }
194
195                 void LoadKey (SecurityElement se)
196                 {
197                         Hashtable h = se.Attributes;
198                         try {
199                                 string name = (string) h ["name"];
200                                 if (name == null)
201                                         return;
202                                 string type = (string) h ["type"];
203                                 if (type == null)
204                                         return;
205                                 
206                                 switch (type){
207                                 case "int":
208                                         values [name] = Int32.Parse (se.Text);
209                                         break;
210                                 case "bytearray":
211                                         values [name] = Convert.FromBase64String (se.Text);
212                                         break;
213                                 case "string":
214                                         values [name] = se.Text == null ? String.Empty : se.Text;
215                                         break;
216                                 case "expand":
217                                         values [name] = new ExpandString (se.Text);
218                                         break;
219                                 case "qword":
220                                         values [name] = Int64.Parse (se.Text);
221                                         break;
222                                 case "string-array":
223                                         var sa = new List<string> ();
224                                         if (se.Children != null){
225                                                 foreach (SecurityElement stre in se.Children){
226                                                         sa.Add (stre.Text);
227                                                 }
228                                         }
229                                         values [name] = sa.ToArray ();
230                                         break;
231                                 }
232                         } catch {
233                                 // We ignore individual errors in the file.
234                         }
235                 }
236
237                 public RegistryKey Ensure (RegistryKey rkey, string extra, bool writable)
238                 {
239                         return Ensure (rkey, extra, writable, false);
240                 }
241
242                 // 'is_volatile' is used only if the key hasn't been created already.
243                 public RegistryKey Ensure (RegistryKey rkey, string extra, bool writable, bool is_volatile)
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                                         kh = new KeyHandler (rkey, f, is_volatile);
250                                 RegistryKey rk = new RegistryKey (kh, CombineName (rkey, extra), writable);
251                                 key_to_handler [rk] = kh;
252                                 dir_to_handler [f] = kh;
253                                 return rk;
254                         }
255                 }
256
257                 public RegistryKey Probe (RegistryKey rkey, string extra, bool writable)
258                 {
259                         RegistryKey rk = null;
260
261                         lock (typeof (KeyHandler)){
262                                 string f = Path.Combine (Dir, extra);
263                                 KeyHandler kh = (KeyHandler) dir_to_handler [f];
264                                 if (kh != null) {
265                                         rk = new RegistryKey (kh, CombineName (rkey,
266                                                 extra), writable);
267                                         key_to_handler [rk] = kh;
268                                 } else if (Directory.Exists (f) || VolatileKeyExists (f)) {
269                                         kh = new KeyHandler (rkey, f);
270                                         rk = new RegistryKey (kh, CombineName (rkey, extra),
271                                                 writable);
272                                         dir_to_handler [f] = kh;
273                                         key_to_handler [rk] = kh;
274                                 }
275                                 return rk;
276                         }
277                 }
278
279                 static string CombineName (RegistryKey rkey, string extra)
280                 {
281                         if (extra.IndexOf ('/') != -1)
282                                 extra = extra.Replace ('/', '\\');
283                         
284                         return String.Concat (rkey.Name, "\\", extra);
285                 }
286
287                 static long GetSystemBootTime ()
288                 {
289                         if (!File.Exists ("/proc/stat"))
290                                 return -1;
291
292                         string btime = null;
293                         string line;
294
295                         try {
296                                 using (StreamReader stat_file = new StreamReader ("/proc/stat", Encoding.ASCII)) {
297                                         while ((line = stat_file.ReadLine ()) != null)
298                                                 if (line.StartsWith ("btime")) {
299                                                         btime = line;
300                                                         break;
301                                                 }
302                                 }
303                         } catch (Exception e) {
304                                 Console.Error.WriteLine ("While reading system info {0}", e);
305                         }
306
307                         if (btime == null)
308                                 return -1;
309
310                         int space = btime.IndexOf (' ');
311                         long res;
312                         if (!Int64.TryParse (btime.Substring (space, btime.Length - space), out res))
313                                 return -1;
314
315                         return res;
316                 }
317
318                 // The registered boot time it's a simple line containing the last system btime.
319                 static long GetRegisteredBootTime (string path)
320                 {
321                         if (!File.Exists (path))
322                                 return -1;
323
324                         string line = null;
325                         try {
326                                 using (StreamReader reader = new StreamReader (path, Encoding.ASCII))
327                                         line = reader.ReadLine ();
328                         } catch (Exception e) {
329                                 Console.Error.WriteLine ("While reading registry data at {0}: {1}", path, e);
330                         }
331
332                         if (line == null)
333                                 return -1;
334
335                         long res;
336                         if (!Int64.TryParse (line, out res))
337                                 return -1;
338
339                         return res;
340                 }
341
342                 static void SaveRegisteredBootTime (string path, long btime)
343                 {
344                         try {
345                                 using (StreamWriter writer = new StreamWriter (path, false, Encoding.ASCII))
346                                         writer.WriteLine (btime.ToString ());
347                         } catch (Exception) {
348                                 /* This can happen when a user process tries to write to MachineStore */
349                                 //Console.Error.WriteLine ("While saving registry data at {0}: {1}", path, e);
350                         }
351                 }
352                         
353                 // We save the last boot time in a last-btime file in every root, and we use it
354                 // to clean the volatile keys directory in case the system btime changed.
355                 static void CleanVolatileKeys ()
356                 {
357                         long system_btime = GetSystemBootTime ();
358
359                         string [] roots = new string [] {
360                                 UserStore,
361                                 MachineStore
362                         };
363
364                         foreach (string root in roots) {
365                                 if (!Directory.Exists (root))
366                                         continue;
367
368                                 string btime_file = Path.Combine (root, "last-btime");
369                                 string volatile_dir = Path.Combine (root, VolatileDirectoryName);
370
371                                 if (Directory.Exists (volatile_dir)) {
372                                         long registered_btime = GetRegisteredBootTime (btime_file);
373                                         if (system_btime < 0 || registered_btime < 0 || registered_btime != system_btime)
374                                                 Directory.Delete (volatile_dir, true);
375                                 }
376
377                                 SaveRegisteredBootTime (btime_file, system_btime);
378                         }
379                 }
380         
381                 public static bool VolatileKeyExists (string dir)
382                 {
383                         lock (typeof (KeyHandler)) {
384                                 KeyHandler kh = (KeyHandler) dir_to_handler [dir];
385                                 if (kh != null)
386                                         return kh.IsVolatile;
387                         }
388
389                         if (Directory.Exists (dir)) // Non-volatile key exists.
390                                 return false;
391
392                         return Directory.Exists (GetVolatileDir (dir));
393                 }
394
395                 public static string GetVolatileDir (string dir)
396                 {
397                         string root = GetRootFromDir (dir);
398                         string volatile_dir = dir.Replace (root, Path.Combine (root, VolatileDirectoryName));
399                         return volatile_dir;
400                 }
401
402                 public static KeyHandler Lookup (RegistryKey rkey, bool createNonExisting)
403                 {
404                         lock (typeof (KeyHandler)){
405                                 KeyHandler k = (KeyHandler) key_to_handler [rkey];
406                                 if (k != null)
407                                         return k;
408
409                                 // when a non-root key is requested for no keyhandler exist
410                                 // then that key must have been marked for deletion
411                                 if (!rkey.IsRoot || !createNonExisting)
412                                         return null;
413
414                                 RegistryHive x = (RegistryHive) rkey.Hive;
415                                 switch (x){
416                                 case RegistryHive.CurrentUser:
417                                         string userDir = Path.Combine (UserStore, x.ToString ());
418                                         k = new KeyHandler (rkey, userDir);
419                                         dir_to_handler [userDir] = k;
420                                         break;
421                                 case RegistryHive.CurrentConfig:
422                                 case RegistryHive.ClassesRoot:
423                                 case RegistryHive.DynData:
424                                 case RegistryHive.LocalMachine:
425                                 case RegistryHive.PerformanceData:
426                                 case RegistryHive.Users:
427                                         string machine_dir = Path.Combine (MachineStore, x.ToString ());
428                                         k = new KeyHandler (rkey, machine_dir);
429                                         dir_to_handler [machine_dir] = k;
430                                         break;
431                                 default:
432                                         throw new Exception ("Unknown RegistryHive");
433                                 }
434                                 key_to_handler [rkey] = k;
435                                 return k;
436                         }
437                 }
438
439                 static string GetRootFromDir (string dir)
440                 {
441                         if (dir.IndexOf (UserStore) > -1)
442                                 return UserStore;
443                         else if (dir.IndexOf (MachineStore) > -1)
444                                 return MachineStore;
445
446                         throw new Exception ("Could not get root for dir " + dir);
447                 }
448
449                 public static void Drop (RegistryKey rkey)
450                 {
451                         lock (typeof (KeyHandler)) {
452                                 KeyHandler k = (KeyHandler) key_to_handler [rkey];
453                                 if (k == null)
454                                         return;
455                                 key_to_handler.Remove (rkey);
456
457                                 // remove cached KeyHandler if no other keys reference it
458                                 int refCount = 0;
459                                 foreach (DictionaryEntry de in key_to_handler)
460                                         if (de.Value == k)
461                                                 refCount++;
462                                 if (refCount == 0)
463                                         dir_to_handler.Remove (k.Dir);
464                         }
465                 }
466
467                 public static void Drop (string dir)
468                 {
469                         lock (typeof (KeyHandler)) {
470                                 KeyHandler kh = (KeyHandler) dir_to_handler [dir];
471                                 if (kh == null)
472                                         return;
473
474                                 dir_to_handler.Remove (dir);
475
476                                 // remove (other) references to keyhandler
477                                 ArrayList keys = new ArrayList ();
478                                 foreach (DictionaryEntry de in key_to_handler)
479                                         if (de.Value == kh)
480                                                 keys.Add (de.Key);
481
482                                 foreach (object key in keys)
483                                         key_to_handler.Remove (key);
484                         }
485                 }
486
487                 public static bool Delete (string dir)
488                 {
489                         if (!Directory.Exists (dir)) {
490                                 string volatile_dir = GetVolatileDir (dir);
491                                 if (!Directory.Exists (volatile_dir))
492                                         return false;
493
494                                 dir = volatile_dir;
495                         }
496
497                         Directory.Delete (dir, true);
498                         Drop (dir);
499                         return true;
500                 }
501
502                 public RegistryValueKind GetValueKind (string name)
503                 {
504                         if (name == null)
505                                 return RegistryValueKind.Unknown;
506                         object value;
507                         
508                         lock (values)
509                                 value = values [name];
510                         
511                         if (value == null)
512                                 return RegistryValueKind.Unknown;
513
514                         if (value is int)
515                                 return RegistryValueKind.DWord;
516                         if (value is string [])
517                                 return RegistryValueKind.MultiString;
518                         if (value is long)
519                                 return RegistryValueKind.QWord;
520                         if (value is byte [])
521                                 return RegistryValueKind.Binary;
522                         if (value is string)
523                                 return RegistryValueKind.String;
524                         if (value is ExpandString)
525                                 return RegistryValueKind.ExpandString;
526                         return RegistryValueKind.Unknown;
527                 }
528                 
529                 public object GetValue (string name, RegistryValueOptions options)
530                 {
531                         if (IsMarkedForDeletion)
532                                 return null;
533
534                         if (name == null)
535                                 name = string.Empty;
536                         object value;
537                         lock (values)
538                                 value = values [name];
539                         ExpandString exp = value as ExpandString;
540                         if (exp == null)
541                                 return value;
542                         if ((options & RegistryValueOptions.DoNotExpandEnvironmentNames) == 0)
543                                 return exp.Expand ();
544
545                         return exp.ToString ();
546                 }
547
548                 public void SetValue (string name, object value)
549                 {
550                         AssertNotMarkedForDeletion ();
551
552                         if (name == null)
553                                 name = string.Empty;
554
555                         lock (values){
556                                 // immediately convert non-native registry values to string to avoid
557                                 // returning it unmodified in calls to UnixRegistryApi.GetValue
558                                 if (value is int || value is string || value is byte[] || value is string[])
559                                         values[name] = value;
560                                 else
561                                         values[name] = value.ToString ();
562                         }
563                         SetDirty ();
564                 }
565
566                 public string [] GetValueNames ()
567                 {
568                         AssertNotMarkedForDeletion ();
569
570                         lock (values){
571                                 ICollection keys = values.Keys;
572                                 
573                                 string [] vals = new string [keys.Count];
574                                 keys.CopyTo (vals, 0);
575                                 return vals;
576                         }
577                 }
578
579                 public int GetSubKeyCount ()
580                 {
581                         return GetSubKeyNames ().Length;
582                 }
583
584                 public string [] GetSubKeyNames ()
585                 {
586                         DirectoryInfo selfDir = new DirectoryInfo (ActualDir);
587                         DirectoryInfo[] subDirs = selfDir.GetDirectories ();
588                         string[] subKeyNames;
589
590                         // for volatile keys (cannot contain non-volatile subkeys) or keys
591                         // without *any* presence in the volatile key section, we can do it simple.
592                         if (IsVolatile || !Directory.Exists (GetVolatileDir (Dir))) {
593                                 subKeyNames = new string[subDirs.Length];
594                                 for (int i = 0; i < subDirs.Length; i++) {
595                                         DirectoryInfo subDir = subDirs[i];
596                                         subKeyNames[i] = subDir.Name;
597                                 }
598                                 return subKeyNames;
599                         }
600
601                         // We may have the entries repeated, so keep just one of each one.
602                         DirectoryInfo volatileDir = new DirectoryInfo (GetVolatileDir (Dir));
603                         DirectoryInfo [] volatileSubDirs = volatileDir.GetDirectories ();
604                         Dictionary<string,string> dirs = new Dictionary<string,string> ();
605
606                         foreach (DirectoryInfo dir in subDirs)
607                                 dirs [dir.Name] = dir.Name;
608                         foreach (DirectoryInfo volDir in volatileSubDirs)
609                                 dirs [volDir.Name] = volDir.Name;
610
611                         subKeyNames = new string [dirs.Count];
612                         int j = 0;
613                         foreach (KeyValuePair<string,string> entry in dirs)
614                                 subKeyNames[j++] = entry.Value;
615
616                         return subKeyNames;
617                 }
618
619                 //
620                 // This version has to do argument validation based on the valueKind
621                 //
622                 public void SetValue (string name, object value, RegistryValueKind valueKind)
623                 {
624                         SetDirty ();
625
626                         if (name == null)
627                                 name = string.Empty;
628
629                         lock (values){
630                                 switch (valueKind){
631                                 case RegistryValueKind.String:
632                                         if (value is string){
633                                                 values [name] = value;
634                                                 return;
635                                         }
636                                         break;
637                                 case RegistryValueKind.ExpandString:
638                                         if (value is string){
639                                                 values [name] = new ExpandString ((string)value);
640                                                 return;
641                                         }
642                                         break;
643                                         
644                                 case RegistryValueKind.Binary:
645                                         if (value is byte []){
646                                                 values [name] = value;
647                                                 return;
648                                         }
649                                         break;
650                                         
651                                 case RegistryValueKind.DWord:
652                                         try {
653                                                 values [name] = Convert.ToInt32 (value);
654                                                 return;
655                                         } catch (OverflowException) {
656                                                 break;
657                                         }
658                                         
659                                 case RegistryValueKind.MultiString:
660                                         if (value is string []){
661                                                 values [name] = value;
662                                                 return;
663                                         }
664                                         break;
665                                         
666                                 case RegistryValueKind.QWord:
667                                         try {
668                                                 values [name] = Convert.ToInt64 (value);
669                                                 return;
670                                         } catch (OverflowException) {
671                                                 break;
672                                         }
673                                         
674                                 default:
675                                         throw new ArgumentException ("unknown value", "valueKind");
676                                 }
677                         }
678                         throw new ArgumentException ("Value could not be converted to specified type", "valueKind");
679                 }
680
681                 void SetDirty ()
682                 {
683                         lock (typeof (KeyHandler)){
684                                 if (dirty)
685                                         return;
686                                 dirty = true;
687                                 new Timer (DirtyTimeout, null, 3000, Timeout.Infinite);
688                         }
689                 }
690
691                 public void DirtyTimeout (object state)
692                 {
693                         Flush ();
694                 }
695
696                 public void Flush ()
697                 {
698                         lock (typeof (KeyHandler)) {
699                                 if (dirty) {
700                                         Save ();
701                                         dirty = false;
702                                 }
703                         }
704                 }
705
706                 public bool ValueExists (string name)
707                 {
708                         if (name == null)
709                                 name = string.Empty;
710
711                         lock (values)
712                                 return values.Contains (name);
713                 }
714
715                 public int ValueCount {
716                         get {
717                                 lock (values)
718                                         return values.Keys.Count;
719                         }
720                 }
721
722                 public bool IsMarkedForDeletion {
723                         get {
724                                 return !dir_to_handler.Contains (Dir);
725                         }
726                 }
727
728                 public void RemoveValue (string name)
729                 {
730                         AssertNotMarkedForDeletion ();
731
732                         lock (values)
733                                 values.Remove (name);
734                         SetDirty ();
735                 }
736
737                 ~KeyHandler ()
738                 {
739                         Flush ();
740                 }
741                 
742                 void Save ()
743                 {
744                         if (IsMarkedForDeletion)
745                                 return;
746
747                         SecurityElement se = new SecurityElement ("values");
748                                 
749                         lock (values){
750                                 if (!File.Exists (file) && values.Count == 0)
751                                         return;
752         
753                                 // With SecurityElement.Text = value, and SecurityElement.AddAttribute(key, value)
754                                 // the values must be escaped prior to being assigned. 
755                                 foreach (DictionaryEntry de in values){
756                                         object val = de.Value;
757                                         SecurityElement value = new SecurityElement ("value");
758                                         value.AddAttribute ("name", SecurityElement.Escape ((string) de.Key));
759                                         
760                                         if (val is string){
761                                                 value.AddAttribute ("type", "string");
762                                                 value.Text = SecurityElement.Escape ((string) val);
763                                         } else if (val is int){
764                                                 value.AddAttribute ("type", "int");
765                                                 value.Text = val.ToString ();
766                                         } else if (val is long) {
767                                                 value.AddAttribute ("type", "qword");
768                                                 value.Text = val.ToString ();
769                                         } else if (val is byte []){
770                                                 value.AddAttribute ("type", "bytearray");
771                                                 value.Text = Convert.ToBase64String ((byte[]) val);
772                                         } else if (val is ExpandString){
773                                                 value.AddAttribute ("type", "expand");
774                                                 value.Text = SecurityElement.Escape (val.ToString ());
775                                         } else if (val is string []){
776                                                 value.AddAttribute ("type", "string-array");
777         
778                                                 foreach (string ss in (string[]) val){
779                                                         SecurityElement str = new SecurityElement ("string");
780                                                         str.Text = SecurityElement.Escape (ss); 
781                                                         value.AddChild (str);
782                                                 }
783                                         }
784                                         se.AddChild (value);
785                                 }
786                         }
787                         
788                         using (FileStream fs = File.Create (file)){
789                                 StreamWriter sw = new StreamWriter (fs);
790
791                                 sw.Write (se.ToString ());
792                                 sw.Flush ();
793                         }
794                 }
795
796                 private void AssertNotMarkedForDeletion ()
797                 {
798                         if (IsMarkedForDeletion)
799                                 throw RegistryKey.CreateMarkedForDeletionException ();
800                 }
801
802                 static string user_store;
803                 static string machine_store;
804
805                 private static string UserStore {
806                         get {
807                                 if (user_store == null)
808                                         user_store = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal),
809                                         ".mono/registry");
810
811                                 return user_store;
812                         }
813                 }
814
815                 private static string MachineStore {
816                         get {
817                                 if (machine_store == null) {
818                                         machine_store = Environment.GetEnvironmentVariable ("MONO_REGISTRY_PATH");
819                                         if (machine_store == null) {
820                                                 string s = Environment.GetMachineConfigPath ();
821                                                 int p = s.IndexOf ("machine.config");
822                                                 machine_store = Path.Combine (Path.Combine (s.Substring (0, p-1), ".."), "registry");
823                                         }
824                                 }
825
826                                 return machine_store;
827                         }
828                 }
829         }
830         
831         internal class UnixRegistryApi : IRegistryApi {
832
833                 static string ToUnix (string keyname)
834                 {
835                         if (keyname.IndexOf ('\\') != -1)
836                                 keyname = keyname.Replace ('\\', '/');
837                         return keyname.ToLower ();
838                 }
839
840                 static bool IsWellKnownKey (string parentKeyName, string keyname)
841                 {
842                         // FIXME: Add more keys if needed
843                         if (parentKeyName == Registry.CurrentUser.Name ||
844                                 parentKeyName == Registry.LocalMachine.Name)
845                                 return (0 == String.Compare ("software", keyname, true, CultureInfo.InvariantCulture));
846
847                         return false;
848                 }
849
850                 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname)
851                 {
852                         return CreateSubKey (rkey, keyname, true);
853                 }
854
855                 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname, RegistryOptions options)
856                 {
857                         return CreateSubKey (rkey, keyname, true, options == RegistryOptions.Volatile);
858                 }
859
860                 public RegistryKey OpenRemoteBaseKey (RegistryHive hKey, string machineName)
861                 {
862                         throw new NotImplementedException ();
863                 }
864
865                 public RegistryKey OpenSubKey (RegistryKey rkey, string keyname, bool writable)
866                 {
867                         KeyHandler self = KeyHandler.Lookup (rkey, true);
868                         if (self == null) {
869                                 // return null if parent is marked for deletion
870                                 return null;
871                         }
872
873                         RegistryKey result = self.Probe (rkey, ToUnix (keyname), writable);
874                         if (result == null && IsWellKnownKey (rkey.Name, keyname)) {
875                                 // create the subkey even if its parent was opened read-only
876                                 result = CreateSubKey (rkey, keyname, writable);
877                         }
878
879                         return result;
880                 }
881
882                 public RegistryKey FromHandle (SafeRegistryHandle handle)
883                 {
884                         throw new NotImplementedException ();
885                 }
886                 
887                 public void Flush (RegistryKey rkey)
888                 {
889                         KeyHandler self = KeyHandler.Lookup (rkey, false);
890                         if (self == null) {
891                                 // we do not need to flush changes as key is marked for deletion
892                                 return;
893                         }
894                         self.Flush ();
895                 }
896                 
897                 public void Close (RegistryKey rkey)
898                 {
899                         KeyHandler.Drop (rkey);
900                 }
901
902                 public object GetValue (RegistryKey rkey, string name, object default_value, RegistryValueOptions options)
903                 {
904                         KeyHandler self = KeyHandler.Lookup (rkey, true);
905                         if (self == null) {
906                                 // key was removed since it was opened
907                                 return default_value;
908                         }
909
910                         if (self.ValueExists (name))
911                                 return self.GetValue (name, options);
912                         return default_value;
913                 }
914                 
915                 public void SetValue (RegistryKey rkey, string name, object value)
916                 {
917                         KeyHandler self = KeyHandler.Lookup (rkey, true);
918                         if (self == null)
919                                 throw RegistryKey.CreateMarkedForDeletionException ();
920                         self.SetValue (name, value);
921                 }
922
923                 public void SetValue (RegistryKey rkey, string name, object value, RegistryValueKind valueKind)
924                 {
925                         KeyHandler self = KeyHandler.Lookup (rkey, true);
926                         if (self == null)
927                                 throw RegistryKey.CreateMarkedForDeletionException ();
928                         self.SetValue (name, value, valueKind);
929                 }
930
931                 public int SubKeyCount (RegistryKey rkey)
932                 {
933                         KeyHandler self = KeyHandler.Lookup (rkey, true);
934                         if (self == null)
935                                 throw RegistryKey.CreateMarkedForDeletionException ();
936                         return self.GetSubKeyCount ();
937                 }
938                 
939                 public int ValueCount (RegistryKey rkey)
940                 {
941                         KeyHandler self = KeyHandler.Lookup (rkey, true);
942                         if (self == null)
943                                 throw RegistryKey.CreateMarkedForDeletionException ();
944                         return self.ValueCount;
945                 }
946                 
947                 public void DeleteValue (RegistryKey rkey, string name, bool throw_if_missing)
948                 {
949                         KeyHandler self = KeyHandler.Lookup (rkey, true);
950                         if (self == null) {
951                                 // if key is marked for deletion, report success regardless of
952                                 // throw_if_missing
953                                 return;
954                         }
955
956                         if (throw_if_missing && !self.ValueExists (name))
957                                 throw new ArgumentException ("the given value does not exist");
958
959                         self.RemoveValue (name);
960                 }
961                 
962                 public void DeleteKey (RegistryKey rkey, string keyname, bool throw_if_missing)
963                 {
964                         KeyHandler self = KeyHandler.Lookup (rkey, true);
965                         if (self == null) {
966                                 // key is marked for deletion
967                                 if (!throw_if_missing)
968                                         return;
969                                 throw new ArgumentException ("the given value does not exist");
970                         }
971
972                         string dir = Path.Combine (self.Dir, ToUnix (keyname));
973                         
974                         if (!KeyHandler.Delete (dir) && throw_if_missing)
975                                 throw new ArgumentException ("the given value does not exist");
976                 }
977                 
978                 public string [] GetSubKeyNames (RegistryKey rkey)
979                 {
980                         KeyHandler self = KeyHandler.Lookup (rkey, true);
981                         return self.GetSubKeyNames ();
982                 }
983                 
984                 public string [] GetValueNames (RegistryKey rkey)
985                 {
986                         KeyHandler self = KeyHandler.Lookup (rkey, true);
987                         if (self == null)
988                                 throw RegistryKey.CreateMarkedForDeletionException ();
989                         return self.GetValueNames ();
990                 }
991
992                 public string ToString (RegistryKey rkey)
993                 {
994                         return rkey.Name;
995                 }
996
997                 private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable)
998                 {
999                         return CreateSubKey (rkey, keyname, writable, false);
1000                 }
1001
1002                 private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable, bool is_volatile)
1003                 {
1004                         KeyHandler self = KeyHandler.Lookup (rkey, true);
1005                         if (self == null){
1006                                 throw RegistryKey.CreateMarkedForDeletionException ();
1007                         }
1008                         if (KeyHandler.VolatileKeyExists (self.Dir) && !is_volatile)
1009                                 throw new IOException ("Cannot create a non volatile subkey under a volatile key.");
1010
1011                         return self.Ensure (rkey, ToUnix (keyname), writable, is_volatile);
1012                 }
1013
1014                 public RegistryValueKind GetValueKind (RegistryKey rkey, string name)
1015                 {
1016                         KeyHandler self = KeyHandler.Lookup (rkey, true);
1017                         if (self != null) 
1018                                 return self.GetValueKind (name);
1019
1020                         // key was removed since it was opened or it does not exist.
1021                         return RegistryValueKind.Unknown;
1022                 }
1023
1024                 public IntPtr GetHandle (RegistryKey key)
1025                 {
1026                         throw new NotImplementedException ();
1027                 }
1028                 
1029         }
1030 }
1031
1032 #endif // NET_2_1
1033