ed62d4b69495ed62f7246813de6e8841d7b8ab86
[mono.git] / mcs / class / corlib / Microsoft.Win32 / UnixRegistryApi.cs
1 //
2 // Microsoft.Win32/IRegistryApi.cs
3 //
4 // Authors:
5 //      Miguel de Icaza (miguel@gnome.org)
6 //
7 // (C) 2005 Novell, Inc (http://www.novell.com)
8 // 
9 // MISSING:
10 //   Someone could the same subkey twice: once read/write once readonly,
11 //   currently since we use a unique hash based on the file name, we are unable
12 //   to have two versions of the same key and hence unable to throw an exception
13 //   if the user tries to write to a read-only key.
14 //
15 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
16 //
17 // Permission is hereby granted, free of charge, to any person obtaining
18 // a copy of this software and associated documentation files (the
19 // "Software"), to deal in the Software without restriction, including
20 // without limitation the rights to use, copy, modify, merge, publish,
21 // distribute, sublicense, and/or sell copies of the Software, and to
22 // permit persons to whom the Software is furnished to do so, subject to
23 // the following conditions:
24 // 
25 // The above copyright notice and this permission notice shall be
26 // included in all copies or substantial portions of the Software.
27 // 
28 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
29 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
30 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
31 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
32 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
33 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
34 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 //
36
37 using System;
38 using System.Collections;
39 using System.Globalization;
40 using System.IO;
41 using System.Text;
42 using System.Runtime.InteropServices;
43 using System.Reflection;
44 using System.Security;
45 using System.Threading;
46
47 namespace Microsoft.Win32 {
48
49         class ExpandString {
50                 string value;
51                 
52                 public ExpandString (string s)
53                 {
54                         value = s;
55                 }
56
57                 public override string ToString ()
58                 {
59                         return value;
60                 }
61
62                 public string Expand ()
63                 {
64                         StringBuilder sb = new StringBuilder ();
65
66                         for (int i = 0; i < value.Length; i++){
67                                 if (value [i] == '%'){
68                                         int j = i + 1;
69                                         for (; j < value.Length; j++){
70                                                 if (value [j] == '%'){
71                                                         string key = value.Substring (i + 1, j - i - 1);
72
73                                                         sb.Append (Environment.GetEnvironmentVariable (key));
74                                                         i += j;
75                                                         break;
76                                                 }
77                                         }
78                                         if (j == value.Length){
79                                                 sb.Append ('%');
80                                         }
81                                 } else {
82                                         sb.Append (value [i]);
83                                 }               
84                         }
85                         return sb.ToString ();
86                 }
87         }
88         class KeyHandler {
89                 static Hashtable key_to_handler = new Hashtable ();
90                 static Hashtable dir_to_key = new Hashtable ();
91                 public string Dir;
92                 public IntPtr Handle;
93
94                 public Hashtable values;
95                 string file;
96                 bool dirty;
97                 bool valid = true;
98                 
99                 KeyHandler (RegistryKey rkey, string basedir)
100                 {
101                         if (!Directory.Exists (basedir)){
102                                 try {
103                                         Directory.CreateDirectory (basedir);
104                                 } catch (Exception e){
105                                         Console.Error.WriteLine ("KeyHandler error while creating directory {0}:\n{1}", basedir, e);
106                                 }
107                         }
108                         Dir = basedir;
109                         file = Path.Combine (Dir, "values.xml");
110                         Load ();
111                 }
112
113                 public void Load ()
114                 {
115                         values = new Hashtable ();
116                         if (!File.Exists (file))
117                                 return;
118                         
119                         try {
120                                 using (FileStream fs = File.OpenRead (file)){
121                                         StreamReader r = new StreamReader (fs);
122                                         string xml = r.ReadToEnd ();
123                                         if (xml.Length == 0)
124                                                 return;
125                                         
126                                         SecurityElement tree = SecurityElement.FromString (xml);
127                                         if (tree.Tag == "values" && tree.Children != null){
128                                                 foreach (SecurityElement value in tree.Children){
129                                                         if (value.Tag == "value"){
130                                                                 LoadKey (value);
131                                                         }
132                                                 }
133                                         }
134                                 }
135                         } catch (Exception e){
136                                 Console.Error.WriteLine ("While loading registry key at {0}: {1}", file, e);
137                                 values.Clear ();
138                         }
139                 }
140
141                 void LoadKey (SecurityElement se)
142                 {
143                         Hashtable h = se.Attributes;
144                         try {
145                                 string name = (string) h ["name"];
146                                 if (name == null)
147                                         return;
148                                 string type = (string) h ["type"];
149                                 if (type == null)
150                                         return;
151                                 
152                                 switch (type){
153                                 case "int":
154                                         values [name] = Int32.Parse (se.Text);
155                                         break;
156                                 case "bytearray":
157                                         Convert.FromBase64String (se.Text);
158                                         break;
159                                 case "string":
160                                         values [name] = se.Text;
161                                         break;
162                                 case "expand":
163                                         values [name] = new ExpandString (se.Text);
164                                         break;
165                                 case "qword":
166                                         values [name] = Int64.Parse (se.Text);
167                                         break;
168                                 case "string-array":
169                                         ArrayList sa = new ArrayList ();
170                                         if (se.Children != null){
171                                                 foreach (SecurityElement stre in se.Children){
172                                                         sa.Add (stre.Text);
173                                                 }
174                                         }
175                                         values [name] = sa.ToArray (typeof (string));
176                                         break;
177                                 }
178                         } catch {
179                                 // We ignore individual errors in the file.
180                         }
181                 }
182                 
183                 public RegistryKey Ensure (RegistryKey rkey, string extra)
184                 {
185                         lock (typeof (KeyHandler)){
186                                 string f = Path.Combine (Dir, extra);
187                                 if (dir_to_key.Contains (f))
188                                         return (RegistryKey) dir_to_key [f];
189
190                                 KeyHandler kh = new KeyHandler (rkey, f);
191                                 RegistryKey rk = new RegistryKey (kh, CombineName (rkey, extra));
192                                 key_to_handler [rk] = kh;
193                                 dir_to_key [f] = rk;
194                                 return rk;
195                         }
196                 }
197
198                 public RegistryKey Probe (RegistryKey rkey, string extra, bool write)
199                 {
200                         lock (typeof (KeyHandler)){
201                                 string f = Path.Combine (Dir, extra);
202                                 if (dir_to_key.Contains (f))
203                                         return (RegistryKey) dir_to_key [f];
204                                 if (Directory.Exists (f)){
205                                         KeyHandler kh = new KeyHandler (rkey, f);
206                                         RegistryKey rk = new RegistryKey (kh, CombineName (rkey, extra));
207                                         dir_to_key [f] = rk;
208                                         key_to_handler [rk] = kh;
209                                         return rk;
210                                 }
211                                 return null;
212                         }
213                 }
214
215                 static string CombineName (RegistryKey rkey, string extra)
216                 {
217                         if (extra.IndexOf ('/') != -1)
218                                 extra = extra.Replace ('/', '\\');
219                         
220                         return String.Concat (rkey.Name, "\\", extra);
221                 }
222                 
223                 public static KeyHandler Lookup (RegistryKey rkey)
224                 {
225                         lock (typeof (KeyHandler)){
226                                 KeyHandler k = (KeyHandler) key_to_handler [rkey];
227                                 if (k != null)
228                                         return k;
229
230                                 RegistryHive x = (RegistryHive) rkey.Data;
231                                 switch (x){
232                                 case RegistryHive.ClassesRoot:
233                                 case RegistryHive.CurrentConfig:
234                                 case RegistryHive.CurrentUser:
235                                 case RegistryHive.DynData:
236                                 case RegistryHive.LocalMachine:
237                                 case RegistryHive.PerformanceData:
238                                 case RegistryHive.Users:
239                                         string d = Path.Combine (RegistryStore, x.ToString ());
240                                         k = new KeyHandler (rkey, d);
241                                         break;
242                                 default:
243                                         throw new Exception ("Unknown RegistryHive");
244                                 }
245                                 key_to_handler [rkey] = k;
246                                 return k;
247                         }
248                 }
249
250                 public static void Drop (RegistryKey rkey)
251                 {
252                         KeyHandler k = (KeyHandler) key_to_handler [rkey];
253                         if (k == null)
254                                 return;
255                         k.valid = false;
256                         dir_to_key.Remove (k.Dir);
257                         key_to_handler.Remove (rkey);
258                 }
259
260                 public static void Drop (string dir)
261                 {
262                         if (dir_to_key.Contains (dir)){
263                                 RegistryKey rkey = (RegistryKey) dir_to_key [dir];
264                                 Drop (rkey);
265                         }
266                 }
267
268                 public void SetValue (string name, object value)
269                 {
270                         // immediately convert non-native registry values to string to avoid
271                         // returning it unmodified in calls to UnixRegistryApi.GetValue
272                         if (value is int || value is string || value is byte[] || value is string[])
273                                 values[name] = value;
274                         else
275                                 values[name] = value.ToString ();
276                         SetDirty ();
277                 }
278
279 #if NET_2_0
280                 //
281                 // This version has to do argument validation based on the valueKind
282                 //
283                 public void SetValue (string name, object value, RegistryValueKind valueKind)
284                 {
285                         SetDirty ();
286                         switch (valueKind){
287                         case RegistryValueKind.String:
288                                 if (value is string){
289                                         values [name] = value;
290                                         return;
291                                 }
292                                 break;
293                         case RegistryValueKind.ExpandString:
294                                 if (value is string){
295                                         Console.WriteLine ("SETTING THIS BAD BOY {0} to {1}", name, "Exp");
296                                         values [name] = new ExpandString ((string)value);
297                                         return;
298                                 }
299                                 break;
300                                 
301                         case RegistryValueKind.Binary:
302                                 if (value is byte []){
303                                         values [name] = value;
304                                         return;
305                                 }
306                                 break;
307                                 
308                         case RegistryValueKind.DWord:
309                                 if (value is long &&
310                                     (((long) value) < Int32.MaxValue) &&
311                                     (((long) value) > Int32.MinValue)){
312                                         values [name] = (int) ((long)value);
313                                         return;
314                                 }
315                                 if (value is int){
316                                         values [name] = value;
317                                         return;
318                                 }
319                                 break;
320                                 
321                         case RegistryValueKind.MultiString:
322                                 if (value is string []){
323                                         values [name] = value;
324                                         return;
325                                 }
326                                 break;
327                                 
328                         case RegistryValueKind.QWord:
329                                 if (value is int){
330                                         values [name] = (long) ((int) value);
331                                         return;
332                                 }
333                                 if (value is long){
334                                         values [name] = value;
335                                         return;
336                                 }
337                                 break;
338                         default:
339                                 throw new ArgumentException ("unknown value", "valueKind");
340                         }
341                         throw new ArgumentException ("Value could not be converted to specified type", "valueKind");
342                 }
343 #endif
344                 
345                 void SetDirty ()
346                 {
347                         lock (typeof (KeyHandler)){
348                                 if (dirty)
349                                         return;
350                                 dirty = true;
351                                 new Timer (DirtyTimeout, null, 3000, Timeout.Infinite);
352                         }
353                 }
354
355                 public void DirtyTimeout (object state)
356                 {
357                         Flush ();
358                 }
359                 
360                 public void Flush ()
361                 {
362                         lock (typeof (KeyHandler)){
363                                 Save ();
364                                 dirty = false;
365                         }
366                 }
367
368                 ~KeyHandler ()
369                 {
370                         Flush ();
371                 }
372                 
373                 void Save ()
374                 {
375                         if (!valid)
376                                 return;
377                         
378                         if (!File.Exists (file) && values.Count == 0)
379                                 return;
380
381                         SecurityElement se = new SecurityElement ("values");
382                         
383                         foreach (DictionaryEntry de in values){
384                                 object val = de.Value;
385                                 SecurityElement value = new SecurityElement ("value");
386                                 value.AddAttribute ("name", (string) de.Key);
387                                 
388                                 if (val is string){
389                                         value.AddAttribute ("type", "string");
390                                         value.Text = (string) val;
391                                 } else if (val is int){
392                                         value.AddAttribute ("type", "int");
393                                         value.Text = val.ToString ();
394                                 } else if (val is long){
395                                         value.AddAttribute ("type", "qword");
396                                         value.Text = val.ToString ();
397                                 } else if (val is byte []){
398                                         value.AddAttribute ("type", "bytearray");
399                                         value.Text = Convert.ToBase64String ((byte[]) val);
400                                 } else if (val is ExpandString){
401                                         value.AddAttribute ("type", "expand");
402                                         value.Text = val.ToString ();
403                                 } else if (val is string []){
404                                         value.AddAttribute ("type", "string-array");
405
406                                         foreach (string ss in (string[]) val){
407                                                 SecurityElement str = new SecurityElement ("string");
408                                                 str.Text = ss; 
409                                                 value.AddChild (str);
410                                         }
411                                 }
412                                 se.AddChild (value);
413                         }
414
415                         try {
416                                 using (FileStream fs = File.Create (file)){
417                                         StreamWriter sw = new StreamWriter (fs);
418
419                                         sw.Write (se.ToString ());
420                                         sw.Flush ();
421                                 }
422                         } catch (Exception e){
423                                 Console.Error.WriteLine ("When saving {0} got {1}", file, e);
424                         }
425                 }
426
427                 public static string RegistryStore {
428                         get {
429                                 return Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal),
430                                         ".mono/registry");
431                         }
432                 }
433
434         }
435         
436         internal class UnixRegistryApi : IRegistryApi {
437
438                 static string ToUnix (string keyname)
439                 {
440                         if (keyname.IndexOf ('\\') != -1)
441                                 keyname = keyname.Replace ('\\', '/');
442                         return keyname.ToLower ();
443                 }
444
445                 static bool IsWellKnownKey (string parentKeyName, string keyname)
446                 {
447                         if (string.Compare ("software", keyname, true, CultureInfo.InvariantCulture) == 0)
448                                 return (parentKeyName == Registry.CurrentUser.Name ||
449                                         parentKeyName == Registry.LocalMachine.Name);
450
451                         // required for event log support
452                         if (string.Compare (@"SYSTEM\CurrentControlSet\Services\EventLog", keyname, true, CultureInfo.InvariantCulture) == 0)
453                                 return (parentKeyName == Registry.LocalMachine.Name);
454
455                         return false;
456                 }
457
458                 public RegistryKey CreateSubKey (RegistryKey rkey, string keyname)
459                 {
460                         KeyHandler self = KeyHandler.Lookup (rkey);
461                         return self.Ensure (rkey, ToUnix (keyname));
462                 }
463
464                 public RegistryKey OpenSubKey (RegistryKey rkey, string keyname, bool writtable)
465                 {
466                         KeyHandler self = KeyHandler.Lookup (rkey);
467                         RegistryKey result = self.Probe (rkey, ToUnix (keyname), writtable);
468                         if (result == null && IsWellKnownKey (rkey.Name, keyname)) {
469                                 result = CreateSubKey (rkey, keyname);
470                         }
471
472                         return result;
473                 }
474                 
475                 public void Flush (RegistryKey rkey)
476                 {
477                         KeyHandler self = KeyHandler.Lookup (rkey);
478                         self.Flush ();
479                 }
480                 
481                 public void Close (RegistryKey rkey)
482                 {
483                         KeyHandler.Drop (rkey);
484                 }
485                 
486                 public object GetValue (RegistryKey rkey, string name, bool return_default_value, object default_value)
487                 {
488                         KeyHandler self = KeyHandler.Lookup (rkey);
489
490                         if (self.values.Contains (name)){
491                                 object r = self.values [name];
492
493                                 if (r is ExpandString){
494                                         return ((ExpandString)r).Expand ();
495                                 }
496                                 
497                                 return r;
498                         }
499                         if (return_default_value)
500                                 return default_value;
501                         return null;
502                 }
503                 
504                 public void SetValue (RegistryKey rkey, string name, object value)
505                 {
506                         KeyHandler self = KeyHandler.Lookup (rkey);
507                         self.SetValue (name, value);
508                 }
509
510 #if NET_2_0
511                 public void SetValue (RegistryKey rkey, string name, object value, RegistryValueKind valueKind)
512                 {
513                         KeyHandler self = KeyHandler.Lookup (rkey);
514                         self.SetValue (name, value, valueKind);
515                 }
516 #endif
517         
518                 public int SubKeyCount (RegistryKey rkey)
519                 {
520                         KeyHandler self = KeyHandler.Lookup (rkey);
521
522                         return Directory.GetDirectories (self.Dir).Length;
523                 }
524                 
525                 public int ValueCount (RegistryKey rkey)
526                 {
527                         KeyHandler self = KeyHandler.Lookup (rkey);
528
529                         return self.values.Keys.Count;
530                 }
531                 
532                 public void DeleteValue (RegistryKey rkey, string name, bool throw_if_missing)
533                 {
534                         KeyHandler self = KeyHandler.Lookup (rkey);
535
536                         if (throw_if_missing && !self.values.Contains (name))
537                                 throw new ArgumentException ("the given value does not exist", "name");
538
539                         self.values.Remove (name);
540                 }
541                 
542                 public void DeleteKey (RegistryKey rkey, string keyname, bool throw_if_missing)
543                 {
544                         KeyHandler self = KeyHandler.Lookup (rkey);
545                         string dir = Path.Combine (self.Dir, ToUnix (keyname));
546
547                         if (Directory.Exists (dir)){
548                                 Directory.Delete (dir, true);
549                                 KeyHandler.Drop (dir);
550                         } else if (throw_if_missing)
551                                 throw new ArgumentException ("the given value does not exist", "value");
552                 }
553                 
554                 public string [] GetSubKeyNames (RegistryKey rkey)
555                 {
556                         KeyHandler self = KeyHandler.Lookup (rkey);
557                         DirectoryInfo selfDir = new DirectoryInfo (self.Dir);
558                         DirectoryInfo[] subDirs = selfDir.GetDirectories ();
559                         string[] subKeyNames = new string[subDirs.Length];
560                         for (int i = 0; i < subDirs.Length; i++) {
561                                 DirectoryInfo subDir = subDirs[i];
562                                 subKeyNames[i] = subDir.Name;
563                         }
564                         return subKeyNames;
565                 }
566                 
567                 public string [] GetValueNames (RegistryKey rkey)
568                 {
569                         KeyHandler self = KeyHandler.Lookup (rkey);
570                         ICollection keys = self.values.Keys;
571
572                         string [] vals = new string [keys.Count];
573                         keys.CopyTo (vals, 0);
574                         return vals;
575                 }
576
577                 public string ToString (RegistryKey rkey)
578                 {
579                         return rkey.Name;
580                 }
581         }
582 }