You've already forked linux-packaging-mono
							
							
		
			
				
	
	
		
			360 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			360 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| //------------------------------------------------------------------------------
 | |
| // <copyright file="LocalDB.cs" company="Microsoft">
 | |
| //     Copyright (c) Microsoft Corporation.  All rights reserved.
 | |
| // </copyright>                                                                
 | |
| // <owner current="true" primary="true">antonam</owner>
 | |
| //------------------------------------------------------------------------------
 | |
| 
 | |
| 
 | |
| namespace System.Data
 | |
| {
 | |
|     using System.Configuration;
 | |
|     using System.Threading;
 | |
|     using System.Runtime.InteropServices;
 | |
|     using System.Data.Common;
 | |
|     using System.Globalization;
 | |
|     using System.Data.SqlClient;
 | |
|     using System.Collections.Generic;
 | |
|     using System.Runtime.CompilerServices;
 | |
|     using System.Text;
 | |
|     using System.Diagnostics;
 | |
|     using System.Security;
 | |
|     using System.Security.Permissions;
 | |
| 
 | |
|     internal static class LocalDBAPI
 | |
|     {
 | |
|         const string const_localDbPrefix = @"(localdb)\";
 | |
|         const string const_partialTrustFlagKey = "ALLOW_LOCALDB_IN_PARTIAL_TRUST";
 | |
| 
 | |
|         static PermissionSet _fullTrust = null;
 | |
|         static bool _partialTrustFlagChecked = false;
 | |
|         static bool _partialTrustAllowed = false;
 | |
| 
 | |
| 
 | |
|         // check if name is in format (localdb)\<InstanceName - not empty> and return instance name if it is
 | |
|         internal static string GetLocalDbInstanceNameFromServerName(string serverName)
 | |
|         {
 | |
|             if (serverName == null)
 | |
|                 return null;
 | |
|             serverName = serverName.TrimStart(); // it can start with spaces if specified in quotes
 | |
|             if (!serverName.StartsWith(const_localDbPrefix, StringComparison.OrdinalIgnoreCase))
 | |
|                 return null;
 | |
|             string instanceName = serverName.Substring(const_localDbPrefix.Length).Trim();
 | |
|             if (instanceName.Length == 0)
 | |
|                 return null;
 | |
|             else
 | |
|                 return instanceName;
 | |
|         }
 | |
| 
 | |
| #if !MONO
 | |
|         internal static void ReleaseDLLHandles()
 | |
|         {
 | |
|             s_userInstanceDLLHandle = IntPtr.Zero;
 | |
|             s_localDBFormatMessage = null;
 | |
|             s_localDBCreateInstance = null;
 | |
|         }
 | |
| 
 | |
|              
 | |
| 
 | |
|         //This is copy of handle that SNI maintains, so we are responsible for freeing it - therefore there we are not using SafeHandle
 | |
|         static IntPtr s_userInstanceDLLHandle = IntPtr.Zero;        
 | |
| 
 | |
|         static object s_dllLock = new object();
 | |
|         
 | |
|         static IntPtr UserInstanceDLLHandle
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 if (s_userInstanceDLLHandle==IntPtr.Zero)
 | |
|                 {
 | |
|                     bool lockTaken = false;
 | |
|                     RuntimeHelpers.PrepareConstrainedRegions();
 | |
|                     try
 | |
|                     {
 | |
|                         Monitor.Enter(s_dllLock, ref lockTaken);
 | |
|                         if (s_userInstanceDLLHandle == IntPtr.Zero)
 | |
|                         {
 | |
|                             SNINativeMethodWrapper.SNIQueryInfo(SNINativeMethodWrapper.QTypes.SNI_QUERY_LOCALDB_HMODULE, ref s_userInstanceDLLHandle);
 | |
|                             if (s_userInstanceDLLHandle != IntPtr.Zero)
 | |
|                             {
 | |
|                                 Bid.Trace("<sc.LocalDBAPI.UserInstanceDLLHandle> LocalDB - handle obtained");                                
 | |
|                             }
 | |
|                             else
 | |
|                             {
 | |
|                                 SNINativeMethodWrapper.SNI_Error sniError = new SNINativeMethodWrapper.SNI_Error();
 | |
|                                 SNINativeMethodWrapper.SNIGetLastError(sniError);
 | |
|                                 throw CreateLocalDBException(errorMessage: Res.GetString("LocalDB_FailedGetDLLHandle"), sniError: (int)sniError.sniError);
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                     finally
 | |
|                     {
 | |
|                         if (lockTaken)
 | |
|                             Monitor.Exit(s_dllLock);
 | |
|                     }
 | |
|                 }               
 | |
|                 return s_userInstanceDLLHandle;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         [SuppressUnmanagedCodeSecurity]
 | |
|         [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
 | |
|         private delegate int LocalDBCreateInstanceDelegate([MarshalAs(UnmanagedType.LPWStr)] string version, [MarshalAs(UnmanagedType.LPWStr)] string instance, UInt32 flags);
 | |
| 
 | |
|         static LocalDBCreateInstanceDelegate s_localDBCreateInstance = null;
 | |
| 
 | |
|         static LocalDBCreateInstanceDelegate LocalDBCreateInstance
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 if (s_localDBCreateInstance==null)
 | |
|                 {
 | |
|                     bool lockTaken = false;
 | |
|                     RuntimeHelpers.PrepareConstrainedRegions();
 | |
|                     try
 | |
|                     {
 | |
|                         Monitor.Enter(s_dllLock, ref lockTaken);
 | |
|                         if (s_localDBCreateInstance == null)
 | |
|                         {
 | |
|                             IntPtr functionAddr = SafeNativeMethods.GetProcAddress(UserInstanceDLLHandle, "LocalDBCreateInstance");
 | |
| 
 | |
|                             if (functionAddr == IntPtr.Zero)
 | |
|                             {
 | |
|                                 int hResult=Marshal.GetLastWin32Error();
 | |
|                                 Bid.Trace("<sc.LocalDBAPI.LocalDBCreateInstance> GetProcAddress for LocalDBCreateInstance error 0x{%X}",hResult);
 | |
|                                 throw CreateLocalDBException(errorMessage: Res.GetString("LocalDB_MethodNotFound"));
 | |
|                             }
 | |
|                             s_localDBCreateInstance = (LocalDBCreateInstanceDelegate)Marshal.GetDelegateForFunctionPointer(functionAddr, typeof(LocalDBCreateInstanceDelegate));
 | |
|                         }
 | |
|                     }                    
 | |
|                     finally
 | |
|                     {
 | |
|                         if (lockTaken)
 | |
|                             Monitor.Exit(s_dllLock);
 | |
|                     }
 | |
|                 }
 | |
|                 return s_localDBCreateInstance;
 | |
|             }
 | |
|         }
 | |
| 
 | |
| 
 | |
|         [SuppressUnmanagedCodeSecurity]
 | |
|         [UnmanagedFunctionPointer(CallingConvention.Cdecl,CharSet=CharSet.Unicode)]
 | |
|         private delegate int LocalDBFormatMessageDelegate(int hrLocalDB, UInt32 dwFlags, UInt32 dwLanguageId, StringBuilder buffer, ref UInt32 buflen);
 | |
| 
 | |
|         static LocalDBFormatMessageDelegate s_localDBFormatMessage = null;
 | |
| 
 | |
|         static LocalDBFormatMessageDelegate LocalDBFormatMessage
 | |
|         {
 | |
|             get
 | |
|             {                
 | |
|                 if (s_localDBFormatMessage==null)
 | |
|                 {
 | |
|                     bool lockTaken = false;
 | |
|                     RuntimeHelpers.PrepareConstrainedRegions();
 | |
|                     try
 | |
|                     {
 | |
|                         Monitor.Enter(s_dllLock, ref lockTaken);
 | |
|                         if (s_localDBFormatMessage == null)
 | |
|                         {
 | |
|                             IntPtr functionAddr = SafeNativeMethods.GetProcAddress(UserInstanceDLLHandle, "LocalDBFormatMessage");
 | |
| 
 | |
|                             if (functionAddr == IntPtr.Zero)
 | |
|                             {
 | |
|                                 // SNI checks for LocalDBFormatMessage during DLL loading, so it is practically impossibe to get this error.
 | |
|                                 int hResult=Marshal.GetLastWin32Error();
 | |
|                                 Bid.Trace("<sc.LocalDBAPI.LocalDBFormatMessage> GetProcAddress for LocalDBFormatMessage error 0x{%X}", hResult);
 | |
|                                 throw CreateLocalDBException(errorMessage: Res.GetString("LocalDB_MethodNotFound"));
 | |
|                             }
 | |
|                             s_localDBFormatMessage = (LocalDBFormatMessageDelegate)Marshal.GetDelegateForFunctionPointer(functionAddr, typeof(LocalDBFormatMessageDelegate));                            
 | |
|                         }
 | |
|                     }                                         
 | |
|                     finally
 | |
|                     {
 | |
|                         if (lockTaken)
 | |
|                             Monitor.Exit(s_dllLock);
 | |
|                     }
 | |
|                 }
 | |
|                 return s_localDBFormatMessage;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         const UInt32 const_LOCALDB_TRUNCATE_ERR_MESSAGE = 1;// flag for LocalDBFormatMessage that indicates that message can be truncated if it does not fit in the buffer
 | |
|         const int const_ErrorMessageBufferSize = 1024;      // Buffer size for Local DB error message, according to Serverless team, 1K will be enough for all messages
 | |
| 
 | |
|         
 | |
|         internal static string GetLocalDBMessage(int hrCode)
 | |
|         {
 | |
|             Debug.Assert(hrCode < 0, "HRCode does not indicate error");
 | |
|             try
 | |
|             {
 | |
|                 StringBuilder buffer = new StringBuilder((int)const_ErrorMessageBufferSize);
 | |
|                 UInt32 len = (UInt32)buffer.Capacity;
 | |
| 
 | |
|                 
 | |
|                 // First try for current culture                
 | |
|                 int hResult=LocalDBFormatMessage(hrLocalDB: hrCode, dwFlags: const_LOCALDB_TRUNCATE_ERR_MESSAGE, dwLanguageId: (UInt32)CultureInfo.CurrentCulture.LCID, 
 | |
|                                                  buffer: buffer, buflen: ref len);
 | |
|                 if (hResult>=0)
 | |
|                     return buffer.ToString();
 | |
|                 else
 | |
|                 {
 | |
|                     // Message is not available for current culture, try default 
 | |
|                     buffer = new StringBuilder((int)const_ErrorMessageBufferSize);
 | |
|                     len = (UInt32) buffer.Capacity;
 | |
|                     hResult=LocalDBFormatMessage(hrLocalDB: hrCode, dwFlags: const_LOCALDB_TRUNCATE_ERR_MESSAGE, dwLanguageId: 0 /* thread locale with fallback to English */,
 | |
|                                                  buffer: buffer, buflen: ref len);
 | |
|                     if (hResult >= 0)
 | |
|                         return buffer.ToString();
 | |
|                     else
 | |
|                         return string.Format(CultureInfo.CurrentCulture, "{0} (0x{1:X}).", Res.GetString("LocalDB_UnobtainableMessage"), hResult);
 | |
|                 }
 | |
|             }
 | |
|             catch (SqlException exc)
 | |
|             {
 | |
|                 return string.Format(CultureInfo.CurrentCulture, "{0} ({1}).", Res.GetString("LocalDB_UnobtainableMessage"), exc.Message);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         
 | |
|         static SqlException CreateLocalDBException(string errorMessage, string instance = null, int localDbError = 0, int sniError = 0)
 | |
|         {
 | |
|             Debug.Assert((localDbError == 0) || (sniError == 0), "LocalDB error and SNI error cannot be specified simultaneously");
 | |
|             Debug.Assert(!string.IsNullOrEmpty(errorMessage), "Error message should not be null or empty");
 | |
|             SqlErrorCollection collection = new SqlErrorCollection();
 | |
| 
 | |
|             int errorCode = (localDbError == 0) ? sniError : localDbError;
 | |
| 
 | |
|             if (sniError!=0)
 | |
|             {
 | |
|                 string sniErrorMessage = SQL.GetSNIErrorMessage(sniError);
 | |
|                 errorMessage = String.Format((IFormatProvider)null, "{0} (error: {1} - {2})",
 | |
|                          errorMessage, sniError, sniErrorMessage);
 | |
|             }
 | |
|             
 | |
|             collection.Add(new SqlError(errorCode, 0, TdsEnums.FATAL_ERROR_CLASS, instance, errorMessage, null, 0));
 | |
| 
 | |
|             if (localDbError != 0)
 | |
|                 collection.Add(new SqlError(errorCode, 0, TdsEnums.FATAL_ERROR_CLASS, instance, GetLocalDBMessage(localDbError),  null, 0));
 | |
| 
 | |
|             SqlException exc = SqlException.CreateException(collection, null);
 | |
| 
 | |
|             exc._doNotReconnect = true;
 | |
| 
 | |
|             return exc;
 | |
|         }
 | |
| 
 | |
|         private class InstanceInfo
 | |
|         {
 | |
|             internal InstanceInfo(string version)
 | |
|             {
 | |
|                 this.version = version;
 | |
|                 this.created = false;
 | |
|             }
 | |
| 
 | |
|             internal readonly string version;
 | |
|             internal bool created;
 | |
|         }
 | |
| 
 | |
|         static object s_configLock=new object();
 | |
|         static Dictionary<string, InstanceInfo> s_configurableInstances = null;
 | |
| 
 | |
|         internal static void DemandLocalDBPermissions()
 | |
|         {
 | |
|             if (!_partialTrustAllowed) 
 | |
|             { 
 | |
|                 if (!_partialTrustFlagChecked) 
 | |
|                 {
 | |
|                     object partialTrustFlagValue = AppDomain.CurrentDomain.GetData(const_partialTrustFlagKey);
 | |
|                     if (partialTrustFlagValue != null && partialTrustFlagValue is bool) 
 | |
|                     {
 | |
|                         _partialTrustAllowed = (bool)partialTrustFlagValue;                        
 | |
|                     }
 | |
|                     _partialTrustFlagChecked = true;        
 | |
|                     if (_partialTrustAllowed) 
 | |
|                     {
 | |
|                         return;
 | |
|                     }
 | |
|                 }
 | |
|                 if (_fullTrust == null) 
 | |
|                 {
 | |
|                     _fullTrust = new NamedPermissionSet("FullTrust");
 | |
|                 }
 | |
|                 _fullTrust.Demand();
 | |
|             }
 | |
|         }        
 | |
| 
 | |
|         internal static void AssertLocalDBPermissions()
 | |
|         {
 | |
|             _partialTrustAllowed = true;
 | |
|         }
 | |
|             
 | |
| 
 | |
|         internal static void CreateLocalDBInstance(string instance)
 | |
|         {
 | |
|             DemandLocalDBPermissions();
 | |
|             if (s_configurableInstances == null)
 | |
|             {
 | |
|                 // load list of instances from configuration, mark them as not created
 | |
|                 bool lockTaken = false;
 | |
|                 RuntimeHelpers.PrepareConstrainedRegions();
 | |
|                 try
 | |
|                 {
 | |
|                     Monitor.Enter(s_configLock, ref lockTaken);
 | |
|                     if (s_configurableInstances == null)
 | |
|                     {
 | |
|                         Dictionary<string, InstanceInfo> tempConfigurableInstances = new Dictionary<string, InstanceInfo>(StringComparer.OrdinalIgnoreCase);
 | |
| #if !NO_CONFIGURATION
 | |
|                         object section = PrivilegedConfigurationManager.GetSection("system.data.localdb");
 | |
|                         if (section != null) // if no section just skip creation
 | |
|                         {
 | |
|                             // validate section type
 | |
|                             LocalDBConfigurationSection configSection = section as LocalDBConfigurationSection;
 | |
|                             if (configSection == null) 
 | |
|                                 throw CreateLocalDBException(errorMessage: Res.GetString("LocalDB_BadConfigSectionType"));
 | |
|                             foreach (LocalDBInstanceElement confElement in configSection.LocalDbInstances)
 | |
|                             {
 | |
|                                 Debug.Assert(confElement.Name != null && confElement.Version != null, "Both name and version should not be null");
 | |
|                                 tempConfigurableInstances.Add(confElement.Name.Trim(), new InstanceInfo(confElement.Version.Trim()));
 | |
|                             }
 | |
|                         }
 | |
|                         else
 | |
| #endif
 | |
|                             Bid.Trace( "<sc.LocalDBAPI.CreateLocalDBInstance> No system.data.localdb section found in configuration");
 | |
|                         s_configurableInstances = tempConfigurableInstances;
 | |
|                     }
 | |
|                 }
 | |
|                 finally
 | |
|                 {
 | |
|                     if (lockTaken)
 | |
|                         Monitor.Exit(s_configLock);
 | |
|                 }
 | |
|             }
 | |
|             
 | |
|             InstanceInfo instanceInfo = null;
 | |
| 
 | |
|             if (!s_configurableInstances.TryGetValue(instance,out instanceInfo))           
 | |
|                 return; // instance name was not in the config
 | |
| 
 | |
|             if (instanceInfo.created)
 | |
|                 return; // instance has already been created
 | |
| 
 | |
|             Debug.Assert(!instance.Contains("\0"), "Instance name should contain embedded nulls");
 | |
| 
 | |
|             if (instanceInfo.version.Contains("\0"))
 | |
|                 throw CreateLocalDBException(errorMessage: Res.GetString("LocalDB_InvalidVersion"), instance: instance);
 | |
|          
 | |
|             // LocalDBCreateInstance is thread- and cross-process safe method, it is OK to call from two threads simultaneously
 | |
|             int hr = LocalDBCreateInstance(instanceInfo.version, instance, flags: 0);
 | |
|             Bid.Trace("<sc.LocalDBAPI.CreateLocalDBInstance> Starting creation of instance %ls version %ls", instance, instanceInfo.version);
 | |
|             if (hr < 0)
 | |
|                 throw CreateLocalDBException(errorMessage: Res.GetString("LocalDB_CreateFailed"), instance: instance, localDbError: hr);
 | |
|             Bid.Trace("<sc.LocalDBAPI.CreateLocalDBInstance> Finished creation of instance %ls", instance);
 | |
|             instanceInfo.created=true; // mark instance as created
 | |
| 
 | |
|         } // CreateLocalDbInstance
 | |
| #endif
 | |
|     }
 | |
| 
 | |
| }
 |