Merge pull request #4051 from Unity-Technologies/registry-bug-fixes
[mono.git] / mcs / class / corlib / Microsoft.Win32 / UnixRegistryApi.cs
index 8c572ced8f79177baa326e79423fe3f806b8b6d4..30a035593a8b4f458c2e3a7adea3d6600ca873ce 100644 (file)
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
 
-#if !NET_2_1
+#if !MOBILE
 
 using System;
 using System.Collections;
+using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
 using System.Text;
@@ -48,6 +49,7 @@ using System.Runtime.InteropServices;
 using System.Reflection;
 using System.Security;
 using System.Threading;
+using Microsoft.Win32.SafeHandles;
 
 namespace Microsoft.Win32 {
 
@@ -91,9 +93,25 @@ namespace Microsoft.Win32 {
                }
        }
 
+       class RegistryKeyComparer : IEqualityComparer {
+               public new bool Equals(object x, object y)
+               {
+                       return RegistryKey.IsEquals ((RegistryKey) x, (RegistryKey) y);
+                       
+               }
+
+               public int GetHashCode(object obj)
+               {
+                       var n = ((RegistryKey) obj).Name;
+                       if (n == null)
+                               return 0;
+                       return n.GetHashCode ();
+               }
+       }
+       
        class KeyHandler
        {
-               static Hashtable key_to_handler = new Hashtable ();
+               static Hashtable key_to_handler = new Hashtable (new RegistryKeyComparer ());
                static Hashtable dir_to_handler = new Hashtable (
                        new CaseInsensitiveHashCodeProvider (), new CaseInsensitiveComparer ());
                const string VolatileDirectoryName = "volatile-keys";
@@ -106,6 +124,11 @@ namespace Microsoft.Win32 {
                string file;
                bool dirty;
 
+               static KeyHandler ()
+               {
+                       CleanVolatileKeys ();
+               }
+
                KeyHandler (RegistryKey rkey, string basedir) : this (rkey, basedir, false)
                {
                }
@@ -127,8 +150,8 @@ namespace Microsoft.Win32 {
                        if (!Directory.Exists (actual_basedir)) {
                                try {
                                        Directory.CreateDirectory (actual_basedir);
-                               } catch (UnauthorizedAccessException){
-                                       throw new SecurityException ("No access to the given key");
+                               } catch (UnauthorizedAccessException ex){
+                                       throw new SecurityException ("No access to the given key", ex);
                                }
                        }
                        Dir = basedir; // This is our identifier.
@@ -197,13 +220,13 @@ namespace Microsoft.Win32 {
                                        values [name] = Int64.Parse (se.Text);
                                        break;
                                case "string-array":
-                                       ArrayList sa = new ArrayList ();
+                                       var sa = new List<string> ();
                                        if (se.Children != null){
                                                foreach (SecurityElement stre in se.Children){
                                                        sa.Add (stre.Text);
                                                }
                                        }
-                                       values [name] = sa.ToArray (typeof (string));
+                                       values [name] = sa.ToArray ();
                                        break;
                                }
                        } catch {
@@ -260,7 +283,101 @@ namespace Microsoft.Win32 {
                        
                        return String.Concat (rkey.Name, "\\", extra);
                }
-                               
+
+               static long GetSystemBootTime ()
+               {
+                       if (!File.Exists ("/proc/stat"))
+                               return -1;
+
+                       string btime = null;
+                       string line;
+
+                       try {
+                               using (StreamReader stat_file = new StreamReader ("/proc/stat", Encoding.ASCII)) {
+                                       while ((line = stat_file.ReadLine ()) != null)
+                                               if (line.StartsWith ("btime")) {
+                                                       btime = line;
+                                                       break;
+                                               }
+                               }
+                       } catch (Exception e) {
+                               Console.Error.WriteLine ("While reading system info {0}", e);
+                       }
+
+                       if (btime == null)
+                               return -1;
+
+                       int space = btime.IndexOf (' ');
+                       long res;
+                       if (!Int64.TryParse (btime.Substring (space, btime.Length - space), out res))
+                               return -1;
+
+                       return res;
+               }
+
+               // The registered boot time it's a simple line containing the last system btime.
+               static long GetRegisteredBootTime (string path)
+               {
+                       if (!File.Exists (path))
+                               return -1;
+
+                       string line = null;
+                       try {
+                               using (StreamReader reader = new StreamReader (path, Encoding.ASCII))
+                                       line = reader.ReadLine ();
+                       } catch (Exception e) {
+                               Console.Error.WriteLine ("While reading registry data at {0}: {1}", path, e);
+                       }
+
+                       if (line == null)
+                               return -1;
+
+                       long res;
+                       if (!Int64.TryParse (line, out res))
+                               return -1;
+
+                       return res;
+               }
+
+               static void SaveRegisteredBootTime (string path, long btime)
+               {
+                       try {
+                               using (StreamWriter writer = new StreamWriter (path, false, Encoding.ASCII))
+                                       writer.WriteLine (btime.ToString ());
+                       } catch (Exception) {
+                               /* This can happen when a user process tries to write to MachineStore */
+                               //Console.Error.WriteLine ("While saving registry data at {0}: {1}", path, e);
+                       }
+               }
+                       
+               // We save the last boot time in a last-btime file in every root, and we use it
+               // to clean the volatile keys directory in case the system btime changed.
+               static void CleanVolatileKeys ()
+               {
+                       long system_btime = GetSystemBootTime ();
+
+                       string [] roots = new string [] {
+                               UserStore,
+                               MachineStore
+                       };
+
+                       foreach (string root in roots) {
+                               if (!Directory.Exists (root))
+                                       continue;
+
+                               string btime_file = Path.Combine (root, "last-btime");
+                               string volatile_dir = Path.Combine (root, VolatileDirectoryName);
+
+                               if (Directory.Exists (volatile_dir)) {
+                                       long registered_btime = GetRegisteredBootTime (btime_file);
+                                       if (system_btime < 0 || registered_btime < 0 || registered_btime != system_btime)
+                                               Directory.Delete (volatile_dir, true);
+                               }
+
+                               SaveRegisteredBootTime (btime_file, system_btime);
+                       }
+               }
+       
                public static bool VolatileKeyExists (string dir)
                {
                        lock (typeof (KeyHandler)) {
@@ -386,7 +503,11 @@ namespace Microsoft.Win32 {
                {
                        if (name == null)
                                return RegistryValueKind.Unknown;
-                       object value = values [name];
+                       object value;
+                       
+                       lock (values)
+                               value = values [name];
+                       
                        if (value == null)
                                return RegistryValueKind.Unknown;
 
@@ -412,7 +533,9 @@ namespace Microsoft.Win32 {
 
                        if (name == null)
                                name = string.Empty;
-                       object value = values [name];
+                       object value;
+                       lock (values)
+                               value = values [name];
                        ExpandString exp = value as ExpandString;
                        if (exp == null)
                                return value;
@@ -429,12 +552,14 @@ namespace Microsoft.Win32 {
                        if (name == null)
                                name = string.Empty;
 
-                       // immediately convert non-native registry values to string to avoid
-                       // returning it unmodified in calls to UnixRegistryApi.GetValue
-                       if (value is int || value is string || value is byte[] || value is string[])
-                               values[name] = value;
-                       else
-                               values[name] = value.ToString ();
+                       lock (values){
+                               // immediately convert non-native registry values to string to avoid
+                               // returning it unmodified in calls to UnixRegistryApi.GetValue
+                               if (value is int || value is string || value is byte[] || value is string[])
+                                       values[name] = value;
+                               else
+                                       values[name] = value.ToString ();
+                       }
                        SetDirty ();
                }
 
@@ -442,11 +567,53 @@ namespace Microsoft.Win32 {
                {
                        AssertNotMarkedForDeletion ();
 
-                       ICollection keys = values.Keys;
+                       lock (values){
+                               ICollection keys = values.Keys;
+                               
+                               string [] vals = new string [keys.Count];
+                               keys.CopyTo (vals, 0);
+                               return vals;
+                       }
+               }
+
+               public int GetSubKeyCount ()
+               {
+                       return GetSubKeyNames ().Length;
+               }
+
+               public string [] GetSubKeyNames ()
+               {
+                       DirectoryInfo selfDir = new DirectoryInfo (ActualDir);
+                       DirectoryInfo[] subDirs = selfDir.GetDirectories ();
+                       string[] subKeyNames;
+
+                       // for volatile keys (cannot contain non-volatile subkeys) or keys
+                       // without *any* presence in the volatile key section, we can do it simple.
+                       if (IsVolatile || !Directory.Exists (GetVolatileDir (Dir))) {
+                               subKeyNames = new string[subDirs.Length];
+                               for (int i = 0; i < subDirs.Length; i++) {
+                                       DirectoryInfo subDir = subDirs[i];
+                                       subKeyNames[i] = subDir.Name;
+                               }
+                               return subKeyNames;
+                       }
+
+                       // We may have the entries repeated, so keep just one of each one.
+                       DirectoryInfo volatileDir = new DirectoryInfo (GetVolatileDir (Dir));
+                       DirectoryInfo [] volatileSubDirs = volatileDir.GetDirectories ();
+                       Dictionary<string,string> dirs = new Dictionary<string,string> ();
+
+                       foreach (DirectoryInfo dir in subDirs)
+                               dirs [dir.Name] = dir.Name;
+                       foreach (DirectoryInfo volDir in volatileSubDirs)
+                               dirs [volDir.Name] = volDir.Name;
 
-                       string [] vals = new string [keys.Count];
-                       keys.CopyTo (vals, 0);
-                       return vals;
+                       subKeyNames = new string [dirs.Count];
+                       int j = 0;
+                       foreach (KeyValuePair<string,string> entry in dirs)
+                               subKeyNames[j++] = entry.Value;
+
+                       return subKeyNames;
                }
 
                //
@@ -459,59 +626,54 @@ namespace Microsoft.Win32 {
                        if (name == null)
                                name = string.Empty;
 
-                       switch (valueKind){
-                       case RegistryValueKind.String:
-                               if (value is string){
-                                       values [name] = value;
-                                       return;
-                               }
-                               break;
-                       case RegistryValueKind.ExpandString:
-                               if (value is string){
-                                       values [name] = new ExpandString ((string)value);
-                                       return;
-                               }
-                               break;
-                               
-                       case RegistryValueKind.Binary:
-                               if (value is byte []){
-                                       values [name] = value;
-                                       return;
-                               }
-                               break;
-                               
-                       case RegistryValueKind.DWord:
-                               if (value is long &&
-                                   (((long) value) < Int32.MaxValue) &&
-                                   (((long) value) > Int32.MinValue)){
-                                       values [name] = (int) ((long)value);
-                                       return;
-                               }
-                               if (value is int){
-                                       values [name] = value;
-                                       return;
-                               }
-                               break;
-                               
-                       case RegistryValueKind.MultiString:
-                               if (value is string []){
-                                       values [name] = value;
-                                       return;
-                               }
-                               break;
-                               
-                       case RegistryValueKind.QWord:
-                               if (value is int){
-                                       values [name] = (long) ((int) value);
-                                       return;
-                               }
-                               if (value is long){
-                                       values [name] = value;
-                                       return;
+                       lock (values){
+                               switch (valueKind){
+                               case RegistryValueKind.String:
+                                       if (value is string){
+                                               values [name] = value;
+                                               return;
+                                       }
+                                       break;
+                               case RegistryValueKind.ExpandString:
+                                       if (value is string){
+                                               values [name] = new ExpandString ((string)value);
+                                               return;
+                                       }
+                                       break;
+                                       
+                               case RegistryValueKind.Binary:
+                                       if (value is byte []){
+                                               values [name] = value;
+                                               return;
+                                       }
+                                       break;
+                                       
+                               case RegistryValueKind.DWord:
+                                       try {
+                                               values [name] = Convert.ToInt32 (value);
+                                               return;
+                                       } catch (OverflowException) {
+                                               break;
+                                       }
+                                       
+                               case RegistryValueKind.MultiString:
+                                       if (value is string []){
+                                               values [name] = value;
+                                               return;
+                                       }
+                                       break;
+                                       
+                               case RegistryValueKind.QWord:
+                                       try {
+                                               values [name] = Convert.ToInt64 (value);
+                                               return;
+                                       } catch (OverflowException) {
+                                               break;
+                                       }
+                                       
+                               default:
+                                       throw new ArgumentException ("unknown value", "valueKind");
                                }
-                               break;
-                       default:
-                               throw new ArgumentException ("unknown value", "valueKind");
                        }
                        throw new ArgumentException ("Value could not be converted to specified type", "valueKind");
                }
@@ -546,12 +708,14 @@ namespace Microsoft.Win32 {
                        if (name == null)
                                name = string.Empty;
 
-                       return values.Contains (name);
+                       lock (values)
+                               return values.Contains (name);
                }
 
                public int ValueCount {
                        get {
-                               return values.Keys.Count;
+                               lock (values)
+                                       return values.Keys.Count;
                        }
                }
 
@@ -565,7 +729,8 @@ namespace Microsoft.Win32 {
                {
                        AssertNotMarkedForDeletion ();
 
-                       values.Remove (name);
+                       lock (values)
+                               values.Remove (name);
                        SetDirty ();
                }
 
@@ -579,45 +744,47 @@ namespace Microsoft.Win32 {
                        if (IsMarkedForDeletion)
                                return;
 
-                       if (!File.Exists (file) && values.Count == 0)
-                               return;
-
                        SecurityElement se = new SecurityElement ("values");
-                       
-                       // With SecurityElement.Text = value, and SecurityElement.AddAttribute(key, value)
-                       // the values must be escaped prior to being assigned. 
-                       foreach (DictionaryEntry de in values){
-                               object val = de.Value;
-                               SecurityElement value = new SecurityElement ("value");
-                               value.AddAttribute ("name", SecurityElement.Escape ((string) de.Key));
                                
-                               if (val is string){
-                                       value.AddAttribute ("type", "string");
-                                       value.Text = SecurityElement.Escape ((string) val);
-                               } else if (val is int){
-                                       value.AddAttribute ("type", "int");
-                                       value.Text = val.ToString ();
-                               } else if (val is long) {
-                                       value.AddAttribute ("type", "qword");
-                                       value.Text = val.ToString ();
-                               } else if (val is byte []){
-                                       value.AddAttribute ("type", "bytearray");
-                                       value.Text = Convert.ToBase64String ((byte[]) val);
-                               } else if (val is ExpandString){
-                                       value.AddAttribute ("type", "expand");
-                                       value.Text = SecurityElement.Escape (val.ToString ());
-                               } else if (val is string []){
-                                       value.AddAttribute ("type", "string-array");
-
-                                       foreach (string ss in (string[]) val){
-                                               SecurityElement str = new SecurityElement ("string");
-                                               str.Text = SecurityElement.Escape (ss); 
-                                               value.AddChild (str);
+                       lock (values){
+                               if (!File.Exists (file) && values.Count == 0)
+                                       return;
+       
+                               // With SecurityElement.Text = value, and SecurityElement.AddAttribute(key, value)
+                               // the values must be escaped prior to being assigned. 
+                               foreach (DictionaryEntry de in values){
+                                       object val = de.Value;
+                                       SecurityElement value = new SecurityElement ("value");
+                                       value.AddAttribute ("name", SecurityElement.Escape ((string) de.Key));
+                                       
+                                       if (val is string){
+                                               value.AddAttribute ("type", "string");
+                                               value.Text = SecurityElement.Escape ((string) val);
+                                       } else if (val is int){
+                                               value.AddAttribute ("type", "int");
+                                               value.Text = val.ToString ();
+                                       } else if (val is long) {
+                                               value.AddAttribute ("type", "qword");
+                                               value.Text = val.ToString ();
+                                       } else if (val is byte []){
+                                               value.AddAttribute ("type", "bytearray");
+                                               value.Text = Convert.ToBase64String ((byte[]) val);
+                                       } else if (val is ExpandString){
+                                               value.AddAttribute ("type", "expand");
+                                               value.Text = SecurityElement.Escape (val.ToString ());
+                                       } else if (val is string []){
+                                               value.AddAttribute ("type", "string-array");
+       
+                                               foreach (string ss in (string[]) val){
+                                                       SecurityElement str = new SecurityElement ("string");
+                                                       str.Text = SecurityElement.Escape (ss); 
+                                                       value.AddChild (str);
+                                               }
                                        }
+                                       se.AddChild (value);
                                }
-                               se.AddChild (value);
                        }
-
+                       
                        using (FileStream fs = File.Create (file)){
                                StreamWriter sw = new StreamWriter (fs);
 
@@ -685,12 +852,10 @@ namespace Microsoft.Win32 {
                        return CreateSubKey (rkey, keyname, true);
                }
 
-#if NET_4_0
                public RegistryKey CreateSubKey (RegistryKey rkey, string keyname, RegistryOptions options)
                {
                        return CreateSubKey (rkey, keyname, true, options == RegistryOptions.Volatile);
                }
-#endif
 
                public RegistryKey OpenRemoteBaseKey (RegistryHive hKey, string machineName)
                {
@@ -713,6 +878,11 @@ namespace Microsoft.Win32 {
 
                        return result;
                }
+
+               public RegistryKey FromHandle (SafeRegistryHandle handle)
+               {
+                       throw new NotImplementedException ();
+               }
                
                public void Flush (RegistryKey rkey)
                {
@@ -763,7 +933,7 @@ namespace Microsoft.Win32 {
                        KeyHandler self = KeyHandler.Lookup (rkey, true);
                        if (self == null)
                                throw RegistryKey.CreateMarkedForDeletionException ();
-                       return Directory.GetDirectories (self.Dir).Length;
+                       return self.GetSubKeyCount ();
                }
                
                public int ValueCount (RegistryKey rkey)
@@ -808,14 +978,7 @@ namespace Microsoft.Win32 {
                public string [] GetSubKeyNames (RegistryKey rkey)
                {
                        KeyHandler self = KeyHandler.Lookup (rkey, true);
-                       DirectoryInfo selfDir = new DirectoryInfo (self.Dir);
-                       DirectoryInfo[] subDirs = selfDir.GetDirectories ();
-                       string[] subKeyNames = new string[subDirs.Length];
-                       for (int i = 0; i < subDirs.Length; i++) {
-                               DirectoryInfo subDir = subDirs[i];
-                               subKeyNames[i] = subDir.Name;
-                       }
-                       return subKeyNames;
+                       return self.GetSubKeyNames ();
                }
                
                public string [] GetValueNames (RegistryKey rkey)
@@ -839,8 +1002,9 @@ namespace Microsoft.Win32 {
                private RegistryKey CreateSubKey (RegistryKey rkey, string keyname, bool writable, bool is_volatile)
                {
                        KeyHandler self = KeyHandler.Lookup (rkey, true);
-                       if (self == null)
+                       if (self == null){
                                throw RegistryKey.CreateMarkedForDeletionException ();
+                       }
                        if (KeyHandler.VolatileKeyExists (self.Dir) && !is_volatile)
                                throw new IOException ("Cannot create a non volatile subkey under a volatile key.");
 
@@ -856,9 +1020,14 @@ namespace Microsoft.Win32 {
                        // key was removed since it was opened or it does not exist.
                        return RegistryValueKind.Unknown;
                }
+
+               public IntPtr GetHandle (RegistryKey key)
+               {
+                       throw new NotImplementedException ();
+               }
                
        }
 }
 
-#endif // NET_2_1
+#endif // MOBILE