1cb4c39eacd0546b35dd423a20445001b4c3542a
[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 ex){
138                                         throw new SecurityException ("No access to the given key", ex);
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                                         var sa = new List<string> ();
208                                         if (se.Children != null){
209                                                 foreach (SecurityElement stre in se.Children){
210                                                         sa.Add (stre.Text);
211                                                 }
212                                         }
213                                         values [name] = sa.ToArray ();
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) {
332                                 /* This can happen when a user process tries to write to MachineStore */
333                                 //Console.Error.WriteLine ("While saving registry data at {0}: {1}", path, e);
334                         }
335                 }
336                         
337                 // We save the last boot time in a last-btime file in every root, and we use it
338                 // to clean the volatile keys directory in case the system btime changed.
339                 static void CleanVolatileKeys ()
340                 {
341                         long system_btime = GetSystemBootTime ();
342
343                         string [] roots = new string [] {
344                                 UserStore,
345                                 MachineStore
346                         };
347
348                         foreach (string root in roots) {
349                                 if (!Directory.Exists (root))
350                                         continue;
351
352                                 string btime_file = Path.Combine (root, "last-btime");
353                                 string volatile_dir = Path.Combine (root, VolatileDirectoryName);
354
355                                 if (Directory.Exists (volatile_dir)) {
356                                         long registered_btime = GetRegisteredBootTime (btime_file);
357                                         if (system_btime < 0 || registered_btime < 0 || registered_btime != system_btime)
358                                                 Directory.Delete (volatile_dir, true);
359                                 }
360
361                                 SaveRegisteredBootTime (btime_file, system_btime);
362                         }
363                 }
364         
365                 public static bool VolatileKeyExists (string dir)
366                 {
367                         lock (typeof (KeyHandler)) {
368                                 KeyHandler kh = (KeyHandler) dir_to_handler [dir];
369                                 if (kh != null)
370                                         return kh.IsVolatile;
371                         }
372
373                         if (Directory.Exists (dir)) // Non-volatile key exists.
374                                 return false;
375
376                         return Directory.Exists (GetVolatileDir (dir));
377                 }
378
379                 public static string GetVolatileDir (string dir)
380                 {
381                         string root = GetRootFromDir (dir);
382                         string volatile_dir = dir.Replace (root, Path.Combine (root, VolatileDirectoryName));
383                         return volatile_dir;
384                 }
385
386                 public static KeyHandler Lookup (RegistryKey rkey, bool createNonExisting)
387                 {
388                         lock (typeof (KeyHandler)){
389                                 KeyHandler k = (KeyHandler) key_to_handler [rkey];
390                                 if (k != null)
391                                         return k;
392
393                                 // when a non-root key is requested for no keyhandler exist
394                                 // then that key must have been marked for deletion
395                                 if (!rkey.IsRoot || !createNonExisting)
396                                         return null;
397
398                                 RegistryHive x = (RegistryHive) rkey.Hive;
399                                 switch (x){
400                                 case RegistryHive.CurrentUser:
401                                         string userDir = Path.Combine (UserStore, x.ToString ());
402                                         k = new KeyHandler (rkey, userDir);
403                                         dir_to_handler [userDir] = k;
404                                         break;
405                                 case RegistryHive.CurrentConfig:
406                                 case RegistryHive.ClassesRoot:
407                                 case RegistryHive.DynData:
408                                 case RegistryHive.LocalMachine:
409                                 case RegistryHive.PerformanceData:
410                                 case RegistryHive.Users:
411                                         string machine_dir = Path.Combine (MachineStore, x.ToString ());
412                                         k = new KeyHandler (rkey, machine_dir);
413                                         dir_to_handler [machine_dir] = k;
414                                         break;
415                                 default:
416                                         throw new Exception ("Unknown RegistryHive");
417                                 }
418                                 key_to_handler [rkey] = k;
419                                 return k;
420                         }
421                 }
422
423                 static string GetRootFromDir (string dir)
424                 {
425                         if (dir.IndexOf (UserStore) > -1)
426                                 return UserStore;
427                         else if (dir.IndexOf (MachineStore) > -1)
428                                 return MachineStore;
429
430                         throw new Exception ("Could not get root for dir " + dir);
431                 }
432
433                 public static void Drop (RegistryKey rkey)
434                 {
435                         lock (typeof (KeyHandler)) {
436                                 KeyHandler k = (KeyHandler) key_to_handler [rkey];
437                                 if (k == null)
438                                         return;
439                                 key_to_handler.Remove (rkey);
440
441                                 // remove cached KeyHandler if no other keys reference it
442                                 int refCount = 0;
443                                 foreach (DictionaryEntry de in key_to_handler)
444                                         if (de.Value == k)
445                                                 refCount++;
446                                 if (refCount == 0)
447                                         dir_to_handler.Remove (k.Dir);
448                         }
449                 }
450
451                 public static void Drop (string dir)
452                 {
453                         lock (typeof (KeyHandler)) {
454                                 KeyHandler kh = (KeyHandler) dir_to_handler [dir];
455                                 if (kh == null)
456                                         return;
457
458                                 dir_to_handler.Remove (dir);
459
460                                 // remove (other) references to keyhandler
461                                 ArrayList keys = new ArrayList ();
462                                 foreach (DictionaryEntry de in key_to_handler)
463                                         if (de.Value == kh)
464                                                 keys.Add (de.Key);
465
466                                 foreach (object key in keys)
467                                         key_to_handler.Remove (key);
468                         }
469                 }
470
471                 public static bool Delete (string dir)
472                 {
473                         if (!Directory.Exists (dir)) {
474                                 string volatile_dir = GetVolatileDir (dir);
475                                 if (!Directory.Exists (volatile_dir))
476                                         return false;
477
478                                 dir = volatile_dir;
479                         }
480
481                         Directory.Delete (dir, true);
482                         Drop (dir);
483                         return true;
484                 }
485
486                 public RegistryValueKind GetValueKind (string name)
487                 {
488                         if (name == null)
489                                 return RegistryValueKind.Unknown;
490                         object value = values [name];
491                         if (value == null)
492                                 return RegistryValueKind.Unknown;
493
494                         if (value is int)
495                                 return RegistryValueKind.DWord;
496                         if (value is string [])
497                                 return RegistryValueKind.MultiString;
498                         if (value is long)
499                                 return RegistryValueKind.QWord;
500                         if (value is byte [])
501                                 return RegistryValueKind.Binary;
502                         if (value is string)
503                                 return RegistryValueKind.String;
504                         if (value is ExpandString)
505                                 return RegistryValueKind.ExpandString;
506                         return RegistryValueKind.Unknown;
507                 }
508                 
509                 public object GetValue (string name, RegistryValueOptions options)
510                 {
511                         if (IsMarkedForDeletion)
512                                 return null;
513
514                         if (name == null)
515                                 name = string.Empty;
516                         object value = values [name];
517                         ExpandString exp = value as ExpandString;
518                         if (exp == null)
519                                 return value;
520                         if ((options & RegistryValueOptions.DoNotExpandEnvironmentNames) == 0)
521                                 return exp.Expand ();
522
523                         return exp.ToString ();
524                 }
525
526                 public void SetValue (string name, object value)
527                 {
528                         AssertNotMarkedForDeletion ();
529
530                         if (name == null)
531                                 name = string.Empty;
532
533                         // immediately convert non-native registry values to string to avoid
534                         // returning it unmodified in calls to UnixRegistryApi.GetValue
535                         if (value is int || value is string || value is byte[] || value is string[])
536                                 values[name] = value;
537                         else
538                                 values[name] = value.ToString ();
539                         SetDirty ();
540                 }
541
542                 public string [] GetValueNames ()
543                 {
544                         AssertNotMarkedForDeletion ();
545
546                         ICollection keys = values.Keys;
547
548                         string [] vals = new string [keys.Count];
549                         keys.CopyTo (vals, 0);
550                         return vals;
551                 }
552
553                 public int GetSubKeyCount ()
554                 {
555                         return GetSubKeyNames ().Length;
556                 }
557
558                 public string [] GetSubKeyNames ()
559                 {
560                         DirectoryInfo selfDir = new DirectoryInfo (ActualDir);
561                         DirectoryInfo[] subDirs = selfDir.GetDirectories ();
562                         string[] subKeyNames;
563
564                         // for volatile keys (cannot contain non-volatile subkeys) or keys
565                         // without *any* presence in the volatile key section, we can do it simple.
566                         if (IsVolatile || !Directory.Exists (GetVolatileDir (Dir))) {
567                                 subKeyNames = new string[subDirs.Length];
568                                 for (int i = 0; i < subDirs.Length; i++) {
569                                         DirectoryInfo subDir = subDirs[i];
570                                         subKeyNames[i] = subDir.Name;
571                                 }
572                                 return subKeyNames;
573                         }
574
575                         // We may have the entries repeated, so keep just one of each one.
576                         DirectoryInfo volatileDir = new DirectoryInfo (GetVolatileDir (Dir));
577                         DirectoryInfo [] volatileSubDirs = volatileDir.GetDirectories ();
578                         Dictionary<string,string> dirs = new Dictionary<string,string> ();
579
580                         foreach (DirectoryInfo dir in subDirs)
581                                 dirs [dir.Name] = dir.Name;
582                         foreach (DirectoryInfo volDir in volatileSubDirs)
583                                 dirs [volDir.Name] = volDir.Name;
584
585                         subKeyNames = new string [dirs.Count];
586                         int j = 0;
587                         foreach (KeyValuePair<string,string> entry in dirs)
588                                 subKeyNames[j++] = entry.Value;
589
590                         return subKeyNames;
591                 }
592
593                 //
594                 // This version has to do argument validation based on the valueKind
595                 //
596                 public void SetValue (string name, object value, RegistryValueKind valueKind)
597                 {
598                         SetDirty ();
599
600                         if (name == null)
601                                 name = string.Empty;
602
603                         switch (valueKind){
604                         case RegistryValueKind.String:
605                                 if (value is string){
606                                         values [name] = value;
607                                         return;
608                                 }
609                                 break;
610                         case RegistryValueKind.ExpandString:
611                                 if (value is string){
612                                         values [name] = new ExpandString ((string)value);
613                                         return;
614                                 }
615                                 break;
616                                 
617                         case RegistryValueKind.Binary:
618                                 if (value is byte []){
619                                         values [name] = value;
620                                         return;
621                                 }
622                                 break;
623                                 
624                         case RegistryValueKind.DWord:
625                                 try {
626                                         values [name] = Convert.ToInt32 (value);
627                                         return;
628                                 } catch (OverflowException) {
629                                         break;
630                                 }
631                                 
632                         case RegistryValueKind.MultiString:
633                                 if (value is string []){
634                                         values [name] = value;
635                                         return;
636                                 }
637                                 break;
638                                 
639                         case RegistryValueKind.QWord:
640                                 try {
641                                         values [name] = Convert.ToInt64 (value);
642                                         return;
643                                 } catch (OverflowException) {
644                                         break;
645                                 }
646                                 
647                         default:
648                                 throw new ArgumentException ("unknown value", "valueKind");
649                         }
650                         throw new ArgumentException ("Value could not be converted to specified type", "valueKind");
651                 }
652
653                 void SetDirty ()
654                 {
655                         lock (typeof (KeyHandler)){
656                                 if (dirty)
657                                         return;
658                                 dirty = true;
659                                 new Timer (DirtyTimeout, null, 3000, Timeout.Infinite);
660                         }
661                 }
662
663                 public void DirtyTimeout (object state)
664                 {
665                         Flush ();
666                 }
667
668                 public void Flush ()
669                 {
670                         lock (typeof (KeyHandler)) {
671                                 if (dirty) {
672                                         Save ();
673                                         dirty = false;
674                                 }
675                         }
676                 }
677
678                 public bool ValueExists (string name)
679                 {
680                         if (name == null)
681                                 name = string.Empty;
682
683                         return values.Contains (name);
684                 }
685
686                 public int ValueCount {
687                         get {
688                                 return values.Keys.Count;
689                         }
690                 }
691
692                 public bool IsMarkedForDeletion {
693                         get {
694                                 return !dir_to_handler.Contains (Dir);
695                         }
696                 }
697
698                 public void RemoveValue (string name)
699                 {
700                         AssertNotMarkedForDeletion ();
701
702                         values.Remove (name);
703                         SetDirty ();
704                 }
705
706                 ~KeyHandler ()
707                 {
708                         Flush ();
709                 }
710                 
711                 void Save ()
712                 {
713                         if (IsMarkedForDeletion)
714                                 return;
715
716                         if (!File.Exists (file) && values.Count == 0)
717                                 return;
718
719                         SecurityElement se = new SecurityElement ("values");
720                         
721                         // With SecurityElement.Text = value, and SecurityElement.AddAttribute(key, value)
722                         // the values must be escaped prior to being assigned. 
723                         foreach (DictionaryEntry de in values){
724                                 object val = de.Value;
725                                 SecurityElement value = new SecurityElement ("value");
726                                 value.AddAttribute ("name", SecurityElement.Escape ((string) de.Key));
727                                 
728                                 if (val is string){
729                                         value.AddAttribute ("type", "string");
730                                         value.Text = SecurityElement.Escape ((string) val);
731                                 } else if (val is int){
732                                         value.AddAttribute ("type", "int");
733                                         value.Text = val.ToString ();
734                                 } else if (val is long) {
735                                         value.AddAttribute ("type", "qword");
736                                         value.Text = val.ToString ();
737                                 } else if (val is byte []){
738                                         value.AddAttribute ("type", "bytearray");
739                                         value.Text = Convert.ToBase64String ((byte[]) val);
740                                 } else if (val is ExpandString){
741                                         value.AddAttribute ("type", "expand");
742                                         value.Text = SecurityElement.Escape (val.ToString ());
743                                 } else if (val is string []){
744                                         value.AddAttribute ("type", "string-array");
745
746                                         foreach (string ss in (string[]) val){
747                                                 SecurityElement str = new SecurityElement ("string");
748                                                 str.Text = SecurityElement.Escape (ss); 
749                                                 value.AddChild (str);
750                                         }
751                                 }
752                                 se.AddChild (value);
753                         }
754
755                         using (FileStream fs = File.Create (file)){
756                                 StreamWriter sw = new StreamWriter (fs);
757
758                                 sw.Write (se.ToString ());
759                                 sw.Flush ();
760                         }
761                 }
762
763                 private void AssertNotMarkedForDeletion ()
764                 {
765                         if (IsMarkedForDeletion)
766                                 throw RegistryKey.CreateMarkedForDeletionException ();
767                 }
768
769                 static string user_store;
770                 static string machine_store;
771
772                 private static string UserStore {
773                         get {
774                                 if (user_store == null)
775                                         user_store = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal),
776                                         ".mono/registry");
777
778                                 return user_store;
779                         }
780                 }
781
782                 private static string MachineStore {
783                         get {
784                                 if (machine_store == null) {
785                                         machine_store = Environment.GetEnvironmentVariable ("MONO_REGISTRY_PATH");
786                                         if (machine_store == null) {
787                                                 string s = Environment.GetMachineConfigPath ();
788                                                 int p = s.IndexOf ("machine.config");
789                                                 machine_store = Path.Combine (Path.Combine (s.Substring (0, p-1), ".."), "registry");
790                                         }
791                                 }
792
793                                 return machine_store;
794                         }
795                 }
796         }
797         
798         internal class UnixRegistryApi : IRegistryApi {
799
800                 static string ToUnix (string keyname)
801                 {
802                         if (keyname.IndexOf ('\\') != -1)
803                                 keyname = keyname.Replace ('\\', '/');
804                         return keyname.ToLower ();
805                 }
806
807                 static bool IsWellKnownKey (string parentKeyName, string keyname)
808                 {
809                         // FIXME: Add more keys if needed
810                         if (parentKeyName == Registry.CurrentUser.Name ||
811                                 parentKeyName == Registry.LocalMachine.Name)
812                                 return (0 == String.Compare ("software", keyname, true, CultureInfo.InvariantCulture));
813
814                         return false;
815                 }
816
817                 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname)
818                 {
819                         return CreateSubKey (rkey, keyname, true);
820                 }
821
822 #if NET_4_0
823                 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname, RegistryOptions options)
824                 {
825                         return CreateSubKey (rkey, keyname, true, options == RegistryOptions.Volatile);
826                 }
827 #endif
828
829                 public RegistryKey OpenRemoteBaseKey (RegistryHive hKey, string machineName)
830                 {
831                         throw new NotImplementedException ();
832                 }
833
834                 public RegistryKey OpenSubKey (RegistryKey rkey, string keyname, bool writable)
835                 {
836                         KeyHandler self = KeyHandler.Lookup (rkey, true);
837                         if (self == null) {
838                                 // return null if parent is marked for deletion
839                                 return null;
840                         }
841
842                         RegistryKey result = self.Probe (rkey, ToUnix (keyname), writable);
843                         if (result == null && IsWellKnownKey (rkey.Name, keyname)) {
844                                 // create the subkey even if its parent was opened read-only
845                                 result = CreateSubKey (rkey, keyname, writable);
846                         }
847
848                         return result;
849                 }
850
851 #if NET_4_0
852                 public RegistryKey FromHandle (SafeRegistryHandle handle)
853                 {
854                         throw new NotImplementedException ();
855                 }
856 #endif
857                 
858                 public void Flush (RegistryKey rkey)
859                 {
860                         KeyHandler self = KeyHandler.Lookup (rkey, false);
861                         if (self == null) {
862                                 // we do not need to flush changes as key is marked for deletion
863                                 return;
864                         }
865                         self.Flush ();
866                 }
867                 
868                 public void Close (RegistryKey rkey)
869                 {
870                         KeyHandler.Drop (rkey);
871                 }
872
873                 public object GetValue (RegistryKey rkey, string name, object default_value, RegistryValueOptions options)
874                 {
875                         KeyHandler self = KeyHandler.Lookup (rkey, true);
876                         if (self == null) {
877                                 // key was removed since it was opened
878                                 return default_value;
879                         }
880
881                         if (self.ValueExists (name))
882                                 return self.GetValue (name, options);
883                         return default_value;
884                 }
885                 
886                 public void SetValue (RegistryKey rkey, string name, object value)
887                 {
888                         KeyHandler self = KeyHandler.Lookup (rkey, true);
889                         if (self == null)
890                                 throw RegistryKey.CreateMarkedForDeletionException ();
891                         self.SetValue (name, value);
892                 }
893
894                 public void SetValue (RegistryKey rkey, string name, object value, RegistryValueKind valueKind)
895                 {
896                         KeyHandler self = KeyHandler.Lookup (rkey, true);
897                         if (self == null)
898                                 throw RegistryKey.CreateMarkedForDeletionException ();
899                         self.SetValue (name, value, valueKind);
900                 }
901
902                 public int SubKeyCount (RegistryKey rkey)
903                 {
904                         KeyHandler self = KeyHandler.Lookup (rkey, true);
905                         if (self == null)
906                                 throw RegistryKey.CreateMarkedForDeletionException ();
907                         return self.GetSubKeyCount ();
908                 }
909                 
910                 public int ValueCount (RegistryKey rkey)
911                 {
912                         KeyHandler self = KeyHandler.Lookup (rkey, true);
913                         if (self == null)
914                                 throw RegistryKey.CreateMarkedForDeletionException ();
915                         return self.ValueCount;
916                 }
917                 
918                 public void DeleteValue (RegistryKey rkey, string name, bool throw_if_missing)
919                 {
920                         KeyHandler self = KeyHandler.Lookup (rkey, true);
921                         if (self == null) {
922                                 // if key is marked for deletion, report success regardless of
923                                 // throw_if_missing
924                                 return;
925                         }
926
927                         if (throw_if_missing && !self.ValueExists (name))
928                                 throw new ArgumentException ("the given value does not exist");
929
930                         self.RemoveValue (name);
931                 }
932                 
933                 public void DeleteKey (RegistryKey rkey, string keyname, bool throw_if_missing)
934                 {
935                         KeyHandler self = KeyHandler.Lookup (rkey, true);
936                         if (self == null) {
937                                 // key is marked for deletion
938                                 if (!throw_if_missing)
939                                         return;
940                                 throw new ArgumentException ("the given value does not exist");
941                         }
942
943                         string dir = Path.Combine (self.Dir, ToUnix (keyname));
944                         
945                         if (!KeyHandler.Delete (dir) && throw_if_missing)
946                                 throw new ArgumentException ("the given value does not exist");
947                 }
948                 
949                 public string [] GetSubKeyNames (RegistryKey rkey)
950                 {
951                         KeyHandler self = KeyHandler.Lookup (rkey, true);
952                         return self.GetSubKeyNames ();
953                 }
954                 
955                 public string [] GetValueNames (RegistryKey rkey)
956                 {
957                         KeyHandler self = KeyHandler.Lookup (rkey, true);
958                         if (self == null)
959                                 throw RegistryKey.CreateMarkedForDeletionException ();
960                         return self.GetValueNames ();
961                 }
962
963                 public string ToString (RegistryKey rkey)
964                 {
965                         return rkey.Name;
966                 }
967
968                 private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable)
969                 {
970                         return CreateSubKey (rkey, keyname, writable, false);
971                 }
972
973                 private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable, bool is_volatile)
974                 {
975                         KeyHandler self = KeyHandler.Lookup (rkey, true);
976                         if (self == null)
977                                 throw RegistryKey.CreateMarkedForDeletionException ();
978                         if (KeyHandler.VolatileKeyExists (self.Dir) && !is_volatile)
979                                 throw new IOException ("Cannot create a non volatile subkey under a volatile key.");
980
981                         return self.Ensure (rkey, ToUnix (keyname), writable, is_volatile);
982                 }
983
984                 public RegistryValueKind GetValueKind (RegistryKey rkey, string name)
985                 {
986                         KeyHandler self = KeyHandler.Lookup (rkey, true);
987                         if (self != null) 
988                                 return self.GetValueKind (name);
989
990                         // key was removed since it was opened or it does not exist.
991                         return RegistryValueKind.Unknown;
992                 }
993
994 #if NET_4_0
995                 public IntPtr GetHandle (RegistryKey key)
996                 {
997                         throw new NotImplementedException ();
998                 }
999 #endif
1000                 
1001         }
1002 }
1003
1004 #endif // NET_2_1
1005