39d5d2e3bbc2f638f93062c27802bfe20800b272
[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.Globalization;
45 using System.IO;
46 using System.Text;
47 using System.Runtime.InteropServices;
48 using System.Reflection;
49 using System.Security;
50 using System.Threading;
51
52 namespace Microsoft.Win32 {
53
54         class ExpandString {
55                 string value;
56                 
57                 public ExpandString (string s)
58                 {
59                         value = s;
60                 }
61
62                 public override string ToString ()
63                 {
64                         return value;
65                 }
66
67                 public string Expand ()
68                 {
69                         StringBuilder sb = new StringBuilder ();
70
71                         for (int i = 0; i < value.Length; i++){
72                                 if (value [i] == '%'){
73                                         int j = i + 1;
74                                         for (; j < value.Length; j++){
75                                                 if (value [j] == '%'){
76                                                         string key = value.Substring (i + 1, j - i - 1);
77
78                                                         sb.Append (Environment.GetEnvironmentVariable (key));
79                                                         i += j;
80                                                         break;
81                                                 }
82                                         }
83                                         if (j == value.Length){
84                                                 sb.Append ('%');
85                                         }
86                                 } else {
87                                         sb.Append (value [i]);
88                                 }
89                         }
90                         return sb.ToString ();
91                 }
92         }
93
94         class KeyHandler
95         {
96                 static Hashtable key_to_handler = new Hashtable ();
97                 static Hashtable dir_to_handler = new Hashtable (
98                         new CaseInsensitiveHashCodeProvider (), new CaseInsensitiveComparer ());
99                 public string Dir;
100
101                 Hashtable values;
102                 string file;
103                 bool dirty;
104
105                 KeyHandler (RegistryKey rkey, string basedir)
106                 {
107                         if (!Directory.Exists (basedir)){
108                                 try {
109                                         Directory.CreateDirectory (basedir);
110                                 } catch (UnauthorizedAccessException){
111                                         throw new SecurityException ("No access to the given key");
112                                 }
113                         }
114                         Dir = basedir;
115                         file = Path.Combine (Dir, "values.xml");
116                         Load ();
117                 }
118
119                 public void Load ()
120                 {
121                         values = new Hashtable ();
122                         if (!File.Exists (file))
123                                 return;
124                         
125                         try {
126                                 using (FileStream fs = File.OpenRead (file)){
127                                         StreamReader r = new StreamReader (fs);
128                                         string xml = r.ReadToEnd ();
129                                         if (xml.Length == 0)
130                                                 return;
131                                         
132                                         SecurityElement tree = SecurityElement.FromString (xml);
133                                         if (tree.Tag == "values" && tree.Children != null){
134                                                 foreach (SecurityElement value in tree.Children){
135                                                         if (value.Tag == "value"){
136                                                                 LoadKey (value);
137                                                         }
138                                                 }
139                                         }
140                                 }
141                         } catch (UnauthorizedAccessException){
142                                 values.Clear ();
143                                 throw new SecurityException ("No access to the given key");
144                         } catch (Exception e){
145                                 Console.Error.WriteLine ("While loading registry key at {0}: {1}", file, e);
146                                 values.Clear ();
147                         }
148                 }
149
150                 void LoadKey (SecurityElement se)
151                 {
152                         Hashtable h = se.Attributes;
153                         try {
154                                 string name = (string) h ["name"];
155                                 if (name == null)
156                                         return;
157                                 string type = (string) h ["type"];
158                                 if (type == null)
159                                         return;
160                                 
161                                 switch (type){
162                                 case "int":
163                                         values [name] = Int32.Parse (se.Text);
164                                         break;
165                                 case "bytearray":
166                                         values [name] = Convert.FromBase64String (se.Text);
167                                         break;
168                                 case "string":
169                                         values [name] = se.Text;
170                                         break;
171                                 case "expand":
172                                         values [name] = new ExpandString (se.Text);
173                                         break;
174                                 case "qword":
175                                         values [name] = Int64.Parse (se.Text);
176                                         break;
177                                 case "string-array":
178                                         ArrayList sa = new ArrayList ();
179                                         if (se.Children != null){
180                                                 foreach (SecurityElement stre in se.Children){
181                                                         sa.Add (stre.Text);
182                                                 }
183                                         }
184                                         values [name] = sa.ToArray (typeof (string));
185                                         break;
186                                 }
187                         } catch {
188                                 // We ignore individual errors in the file.
189                         }
190                 }
191                 
192                 public RegistryKey Ensure (RegistryKey rkey, string extra, bool writable)
193                 {
194                         lock (typeof (KeyHandler)){
195                                 string f = Path.Combine (Dir, extra);
196                                 KeyHandler kh = (KeyHandler) dir_to_handler [f];
197                                 if (kh == null)
198                                         kh = new KeyHandler (rkey, f);
199                                 RegistryKey rk = new RegistryKey (kh, CombineName (rkey, extra), writable);
200                                 key_to_handler [rk] = kh;
201                                 dir_to_handler [f] = kh;
202                                 return rk;
203                         }
204                 }
205
206                 public RegistryKey Probe (RegistryKey rkey, string extra, bool writable)
207                 {
208                         RegistryKey rk = null;
209
210                         lock (typeof (KeyHandler)){
211                                 string f = Path.Combine (Dir, extra);
212                                 KeyHandler kh = (KeyHandler) dir_to_handler [f];
213                                 if (kh != null) {
214                                         rk = new RegistryKey (kh, CombineName (rkey,
215                                                 extra), writable);
216                                         key_to_handler [rk] = kh;
217                                 } else if (Directory.Exists (f)) {
218                                         kh = new KeyHandler (rkey, f);
219                                         rk = new RegistryKey (kh, CombineName (rkey, extra),
220                                                 writable);
221                                         dir_to_handler [f] = kh;
222                                         key_to_handler [rk] = kh;
223                                 }
224                                 return rk;
225                         }
226                 }
227
228                 static string CombineName (RegistryKey rkey, string extra)
229                 {
230                         if (extra.IndexOf ('/') != -1)
231                                 extra = extra.Replace ('/', '\\');
232                         
233                         return String.Concat (rkey.Name, "\\", extra);
234                 }
235                 
236                 public static KeyHandler Lookup (RegistryKey rkey, bool createNonExisting)
237                 {
238                         lock (typeof (KeyHandler)){
239                                 KeyHandler k = (KeyHandler) key_to_handler [rkey];
240                                 if (k != null)
241                                         return k;
242
243                                 // when a non-root key is requested for no keyhandler exist
244                                 // then that key must have been marked for deletion
245                                 if (!rkey.IsRoot || !createNonExisting)
246                                         return null;
247
248                                 RegistryHive x = (RegistryHive) rkey.Hive;
249                                 switch (x){
250                                 case RegistryHive.CurrentUser:
251                                         string userDir = Path.Combine (UserStore, x.ToString ());
252                                         k = new KeyHandler (rkey, userDir);
253                                         dir_to_handler [userDir] = k;
254                                         break;
255                                 case RegistryHive.CurrentConfig:
256                                 case RegistryHive.ClassesRoot:
257                                 case RegistryHive.DynData:
258                                 case RegistryHive.LocalMachine:
259                                 case RegistryHive.PerformanceData:
260                                 case RegistryHive.Users:
261                                         string machine_dir = Path.Combine (MachineStore, x.ToString ());
262                                         k = new KeyHandler (rkey, machine_dir);
263                                         dir_to_handler [machine_dir] = k;
264                                         break;
265                                 default:
266                                         throw new Exception ("Unknown RegistryHive");
267                                 }
268                                 key_to_handler [rkey] = k;
269                                 return k;
270                         }
271                 }
272
273                 public static void Drop (RegistryKey rkey)
274                 {
275                         lock (typeof (KeyHandler)) {
276                                 KeyHandler k = (KeyHandler) key_to_handler [rkey];
277                                 if (k == null)
278                                         return;
279                                 key_to_handler.Remove (rkey);
280
281                                 // remove cached KeyHandler if no other keys reference it
282                                 int refCount = 0;
283                                 foreach (DictionaryEntry de in key_to_handler)
284                                         if (de.Value == k)
285                                                 refCount++;
286                                 if (refCount == 0)
287                                         dir_to_handler.Remove (k.Dir);
288                         }
289                 }
290
291                 public static void Drop (string dir)
292                 {
293                         lock (typeof (KeyHandler)) {
294                                 KeyHandler kh = (KeyHandler) dir_to_handler [dir];
295                                 if (kh == null)
296                                         return;
297
298                                 dir_to_handler.Remove (dir);
299
300                                 // remove (other) references to keyhandler
301                                 ArrayList keys = new ArrayList ();
302                                 foreach (DictionaryEntry de in key_to_handler)
303                                         if (de.Value == kh)
304                                                 keys.Add (de.Key);
305
306                                 foreach (object key in keys)
307                                         key_to_handler.Remove (key);
308                         }
309                 }
310
311                 public object GetValue (string name, RegistryValueOptions options)
312                 {
313                         if (IsMarkedForDeletion)
314                                 return null;
315
316                         if (name == null)
317                                 name = string.Empty;
318                         object value = values [name];
319                         ExpandString exp = value as ExpandString;
320                         if (exp == null)
321                                 return value;
322                         if ((options & RegistryValueOptions.DoNotExpandEnvironmentNames) == 0)
323                                 return exp.Expand ();
324
325                         return exp.ToString ();
326                 }
327
328                 public void SetValue (string name, object value)
329                 {
330                         AssertNotMarkedForDeletion ();
331
332                         if (name == null)
333                                 name = string.Empty;
334
335                         // immediately convert non-native registry values to string to avoid
336                         // returning it unmodified in calls to UnixRegistryApi.GetValue
337                         if (value is int || value is string || value is byte[] || value is string[])
338                                 values[name] = value;
339                         else
340                                 values[name] = value.ToString ();
341                         SetDirty ();
342                 }
343
344                 public string [] GetValueNames ()
345                 {
346                         AssertNotMarkedForDeletion ();
347
348                         ICollection keys = values.Keys;
349
350                         string [] vals = new string [keys.Count];
351                         keys.CopyTo (vals, 0);
352                         return vals;
353                 }
354
355 #if NET_2_0
356                 //
357                 // This version has to do argument validation based on the valueKind
358                 //
359                 public void SetValue (string name, object value, RegistryValueKind valueKind)
360                 {
361                         SetDirty ();
362
363                         if (name == null)
364                                 name = string.Empty;
365
366                         switch (valueKind){
367                         case RegistryValueKind.String:
368                                 if (value is string){
369                                         values [name] = value;
370                                         return;
371                                 }
372                                 break;
373                         case RegistryValueKind.ExpandString:
374                                 if (value is string){
375                                         values [name] = new ExpandString ((string)value);
376                                         return;
377                                 }
378                                 break;
379                                 
380                         case RegistryValueKind.Binary:
381                                 if (value is byte []){
382                                         values [name] = value;
383                                         return;
384                                 }
385                                 break;
386                                 
387                         case RegistryValueKind.DWord:
388                                 if (value is long &&
389                                     (((long) value) < Int32.MaxValue) &&
390                                     (((long) value) > Int32.MinValue)){
391                                         values [name] = (int) ((long)value);
392                                         return;
393                                 }
394                                 if (value is int){
395                                         values [name] = value;
396                                         return;
397                                 }
398                                 break;
399                                 
400                         case RegistryValueKind.MultiString:
401                                 if (value is string []){
402                                         values [name] = value;
403                                         return;
404                                 }
405                                 break;
406                                 
407                         case RegistryValueKind.QWord:
408                                 if (value is int){
409                                         values [name] = (long) ((int) value);
410                                         return;
411                                 }
412                                 if (value is long){
413                                         values [name] = value;
414                                         return;
415                                 }
416                                 break;
417                         default:
418                                 throw new ArgumentException ("unknown value", "valueKind");
419                         }
420                         throw new ArgumentException ("Value could not be converted to specified type", "valueKind");
421                 }
422 #endif
423
424                 void SetDirty ()
425                 {
426                         lock (typeof (KeyHandler)){
427                                 if (dirty)
428                                         return;
429                                 dirty = true;
430                                 new Timer (DirtyTimeout, null, 3000, Timeout.Infinite);
431                         }
432                 }
433
434                 public void DirtyTimeout (object state)
435                 {
436                         Flush ();
437                 }
438
439                 public void Flush ()
440                 {
441                         lock (typeof (KeyHandler)) {
442                                 if (dirty) {
443                                         Save ();
444                                         dirty = false;
445                                 }
446                         }
447                 }
448
449                 public bool ValueExists (string name)
450                 {
451                         if (name == null)
452                                 name = string.Empty;
453
454                         return values.Contains (name);
455                 }
456
457                 public int ValueCount {
458                         get {
459                                 return values.Keys.Count;
460                         }
461                 }
462
463                 public bool IsMarkedForDeletion {
464                         get {
465                                 return !dir_to_handler.Contains (Dir);
466                         }
467                 }
468
469                 public void RemoveValue (string name)
470                 {
471                         AssertNotMarkedForDeletion ();
472
473                         values.Remove (name);
474                         SetDirty ();
475                 }
476
477                 ~KeyHandler ()
478                 {
479                         Flush ();
480                 }
481                 
482                 void Save ()
483                 {
484                         if (IsMarkedForDeletion)
485                                 return;
486
487                         if (!File.Exists (file) && values.Count == 0)
488                                 return;
489
490                         SecurityElement se = new SecurityElement ("values");
491                         
492                         // With SecurityElement.Text = value, and SecurityElement.AddAttribute(key, value)
493                         // the values must be escaped prior to being assigned. 
494                         foreach (DictionaryEntry de in values){
495                                 object val = de.Value;
496                                 SecurityElement value = new SecurityElement ("value");
497                                 value.AddAttribute ("name", SecurityElement.Escape ((string) de.Key));
498                                 
499                                 if (val is string){
500                                         value.AddAttribute ("type", "string");
501                                         value.Text = SecurityElement.Escape ((string) val);
502                                 } else if (val is int){
503                                         value.AddAttribute ("type", "int");
504                                         value.Text = val.ToString ();
505                                 } else if (val is long) {
506                                         value.AddAttribute ("type", "qword");
507                                         value.Text = val.ToString ();
508                                 } else if (val is byte []){
509                                         value.AddAttribute ("type", "bytearray");
510                                         value.Text = Convert.ToBase64String ((byte[]) val);
511                                 } else if (val is ExpandString){
512                                         value.AddAttribute ("type", "expand");
513                                         value.Text = SecurityElement.Escape (val.ToString ());
514                                 } else if (val is string []){
515                                         value.AddAttribute ("type", "string-array");
516
517                                         foreach (string ss in (string[]) val){
518                                                 SecurityElement str = new SecurityElement ("string");
519                                                 str.Text = SecurityElement.Escape (ss); 
520                                                 value.AddChild (str);
521                                         }
522                                 }
523                                 se.AddChild (value);
524                         }
525
526                         using (FileStream fs = File.Create (file)){
527                                 StreamWriter sw = new StreamWriter (fs);
528
529                                 sw.Write (se.ToString ());
530                                 sw.Flush ();
531                         }
532                 }
533
534                 private void AssertNotMarkedForDeletion ()
535                 {
536                         if (IsMarkedForDeletion)
537                                 throw RegistryKey.CreateMarkedForDeletionException ();
538                 }
539
540                 private static string UserStore {
541                         get {
542                                 return Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal),
543                                         ".mono/registry");
544                         }
545                 }
546
547                 private static string MachineStore {
548                         get {
549                                 string s;
550
551                                 s = Environment.GetEnvironmentVariable ("MONO_REGISTRY_PATH");
552                                 if (s != null)
553                                         return s;
554                                 s = Environment.GetMachineConfigPath ();
555                                 int p = s.IndexOf ("machine.config");
556                                 return Path.Combine (Path.Combine (s.Substring (0, p-1), ".."), "registry");
557                         }
558                 }
559         }
560         
561         internal class UnixRegistryApi : IRegistryApi {
562
563                 static string ToUnix (string keyname)
564                 {
565                         if (keyname.IndexOf ('\\') != -1)
566                                 keyname = keyname.Replace ('\\', '/');
567                         return keyname.ToLower ();
568                 }
569
570                 static bool IsWellKnownKey (string parentKeyName, string keyname)
571                 {
572                         // FIXME: Add more keys if needed
573                         if (parentKeyName == Registry.CurrentUser.Name ||
574                                 parentKeyName == Registry.LocalMachine.Name)
575                                 return (0 == String.Compare ("software", keyname, true, CultureInfo.InvariantCulture));
576
577                         return false;
578                 }
579
580                 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname)
581                 {
582                         return CreateSubKey (rkey, keyname, true);
583                 }
584
585                 public RegistryKey OpenRemoteBaseKey (RegistryHive hKey, string machineName)
586                 {
587                         throw new NotImplementedException ();
588                 }
589
590                 public RegistryKey OpenSubKey (RegistryKey rkey, string keyname, bool writable)
591                 {
592                         KeyHandler self = KeyHandler.Lookup (rkey, true);
593                         if (self == null) {
594                                 // return null if parent is marked for deletion
595                                 return null;
596                         }
597
598                         RegistryKey result = self.Probe (rkey, ToUnix (keyname), writable);
599                         if (result == null && IsWellKnownKey (rkey.Name, keyname)) {
600                                 // create the subkey even if its parent was opened read-only
601                                 result = CreateSubKey (rkey, keyname, writable);
602                         }
603
604                         return result;
605                 }
606                 
607                 public void Flush (RegistryKey rkey)
608                 {
609                         KeyHandler self = KeyHandler.Lookup (rkey, false);
610                         if (self == null) {
611                                 // we do not need to flush changes as key is marked for deletion
612                                 return;
613                         }
614                         self.Flush ();
615                 }
616                 
617                 public void Close (RegistryKey rkey)
618                 {
619                         KeyHandler.Drop (rkey);
620                 }
621
622                 public object GetValue (RegistryKey rkey, string name, object default_value, RegistryValueOptions options)
623                 {
624                         KeyHandler self = KeyHandler.Lookup (rkey, true);
625                         if (self == null) {
626                                 // key was removed since it was opened
627                                 return default_value;
628                         }
629
630                         if (self.ValueExists (name))
631                                 return self.GetValue (name, options);
632                         return default_value;
633                 }
634                 
635                 public void SetValue (RegistryKey rkey, string name, object value)
636                 {
637                         KeyHandler self = KeyHandler.Lookup (rkey, true);
638                         if (self == null)
639                                 throw RegistryKey.CreateMarkedForDeletionException ();
640                         self.SetValue (name, value);
641                 }
642
643 #if NET_2_0
644                 public void SetValue (RegistryKey rkey, string name, object value, RegistryValueKind valueKind)
645                 {
646                         KeyHandler self = KeyHandler.Lookup (rkey, true);
647                         if (self == null)
648                                 throw RegistryKey.CreateMarkedForDeletionException ();
649                         self.SetValue (name, value, valueKind);
650                 }
651 #endif
652
653                 public int SubKeyCount (RegistryKey rkey)
654                 {
655                         KeyHandler self = KeyHandler.Lookup (rkey, true);
656                         if (self == null)
657                                 throw RegistryKey.CreateMarkedForDeletionException ();
658                         return Directory.GetDirectories (self.Dir).Length;
659                 }
660                 
661                 public int ValueCount (RegistryKey rkey)
662                 {
663                         KeyHandler self = KeyHandler.Lookup (rkey, true);
664                         if (self == null)
665                                 throw RegistryKey.CreateMarkedForDeletionException ();
666                         return self.ValueCount;
667                 }
668                 
669                 public void DeleteValue (RegistryKey rkey, string name, bool throw_if_missing)
670                 {
671                         KeyHandler self = KeyHandler.Lookup (rkey, true);
672                         if (self == null) {
673                                 // if key is marked for deletion, report success regardless of
674                                 // throw_if_missing
675                                 return;
676                         }
677
678                         if (throw_if_missing && !self.ValueExists (name))
679                                 throw new ArgumentException ("the given value does not exist");
680
681                         self.RemoveValue (name);
682                 }
683                 
684                 public void DeleteKey (RegistryKey rkey, string keyname, bool throw_if_missing)
685                 {
686                         KeyHandler self = KeyHandler.Lookup (rkey, true);
687                         if (self == null) {
688                                 // key is marked for deletion
689                                 if (!throw_if_missing)
690                                         return;
691                                 throw new ArgumentException ("the given value does not exist");
692                         }
693
694                         string dir = Path.Combine (self.Dir, ToUnix (keyname));
695                         
696                         if (Directory.Exists (dir)){
697                                 Directory.Delete (dir, true);
698                                 KeyHandler.Drop (dir);
699                         } else if (throw_if_missing)
700                                 throw new ArgumentException ("the given value does not exist");
701                 }
702                 
703                 public string [] GetSubKeyNames (RegistryKey rkey)
704                 {
705                         KeyHandler self = KeyHandler.Lookup (rkey, true);
706                         DirectoryInfo selfDir = new DirectoryInfo (self.Dir);
707                         DirectoryInfo[] subDirs = selfDir.GetDirectories ();
708                         string[] subKeyNames = new string[subDirs.Length];
709                         for (int i = 0; i < subDirs.Length; i++) {
710                                 DirectoryInfo subDir = subDirs[i];
711                                 subKeyNames[i] = subDir.Name;
712                         }
713                         return subKeyNames;
714                 }
715                 
716                 public string [] GetValueNames (RegistryKey rkey)
717                 {
718                         KeyHandler self = KeyHandler.Lookup (rkey, true);
719                         if (self == null)
720                                 throw RegistryKey.CreateMarkedForDeletionException ();
721                         return self.GetValueNames ();
722                 }
723
724                 public string ToString (RegistryKey rkey)
725                 {
726                         return rkey.Name;
727                 }
728
729                 private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable)
730                 {
731                         KeyHandler self = KeyHandler.Lookup (rkey, true);
732                         if (self == null)
733                                 throw RegistryKey.CreateMarkedForDeletionException ();
734                         return self.Ensure (rkey, ToUnix (keyname), writable);
735                 }
736         }
737 }
738
739 #endif // NET_2_1
740