2015-04-07 09:35:12 +01:00
//------------------------------------------------------------------------------
// <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 ;
}
2015-08-26 07:17:56 -04:00
#if ! MONO
2015-04-07 09:35:12 +01:00
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 ) ;
2015-08-26 07:17:56 -04:00
#if ! NO_CONFIGURATION
2015-04-07 09:35:12 +01:00
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
2015-08-26 07:17:56 -04:00
#endif
2015-04-07 09:35:12 +01:00
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
2015-08-26 07:17:56 -04:00
#endif
2015-04-07 09:35:12 +01:00
}
}