diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 6246fc590d..7f30cb6ab3 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -624,7 +624,6 @@ - @@ -779,6 +778,9 @@ Microsoft\Data\Sql\SqlDataSourceEnumerator.Windows.cs + + Microsoft\Data\SqlClient\LocalDBAPI.Windows.cs + Microsoft\Data\SqlClient\SqlColumnEncryptionCngProvider.Windows.cs @@ -797,9 +799,7 @@ Microsoft\Data\SqlTypes\SqlFileStream.Windows.cs - - - + @@ -816,9 +816,11 @@ Microsoft\Data\ProviderBase\DbConnectionPoolIdentity.Unix.cs + + Microsoft\Data\SqlClient\LocalDBAPI.Unix.cs + - diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Common.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Common.cs deleted file mode 100644 index 616aad0ec1..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Common.cs +++ /dev/null @@ -1,124 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Diagnostics; -using System.Globalization; -using System.Runtime.InteropServices; -using System.Text; -using Microsoft.Data.SqlClient; - -namespace Microsoft.Data -{ - internal static partial class LocalDBAPI - { - private static LocalDBFormatMessageDelegate s_localDBFormatMessage = null; - - internal static void ReleaseDLLHandles() - { - s_userInstanceDLLHandle = IntPtr.Zero; - s_localDBFormatMessage = null; - } - - - private static LocalDBFormatMessageDelegate LocalDBFormatMessage - { - get - { - if (s_localDBFormatMessage == null) - { - lock (s_dllLock) - { - if (s_localDBFormatMessage == null) - { - IntPtr functionAddr = LoadProcAddress(); - - if (functionAddr == IntPtr.Zero) - { - // SNI checks for LocalDBFormatMessage during DLL loading, so it is practically impossible to get this error. - int hResult = Marshal.GetLastWin32Error(); - SqlClientEventSource.Log.TryTraceEvent("LocalDBAPI.LocalDBFormatMessage> GetProcAddress for LocalDBFormatMessage error 0x{0}", hResult); - throw CreateLocalDBException(errorMessage: Strings.LocalDB_MethodNotFound); - } - s_localDBFormatMessage = Marshal.GetDelegateForFunctionPointer(functionAddr); - } - } - } - return s_localDBFormatMessage; - } - } - - //This is copy of handle that SNI maintains, so we are responsible for freeing it - therefore there we are not using SafeHandle - private static IntPtr s_userInstanceDLLHandle = IntPtr.Zero; - - private static readonly object s_dllLock = new object(); - - - private const uint const_LOCALDB_TRUNCATE_ERR_MESSAGE = 1;// flag for LocalDBFormatMessage that indicates that message can be truncated if it does not fit in the buffer - private const int const_ErrorMessageBufferSize = 1024; // Buffer size for Local DB error message 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); - uint len = (uint)buffer.Capacity; - - - // First try for current culture - int hResult = LocalDBFormatMessage(hrLocalDB: hrCode, dwFlags: const_LOCALDB_TRUNCATE_ERR_MESSAGE, dwLanguageId: (uint)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 = (uint)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}).", Strings.LocalDB_UnobtainableMessage, hResult); - } - } - catch (SqlException exc) - { - return string.Format(CultureInfo.CurrentCulture, "{0} ({1}).", Strings.LocalDB_UnobtainableMessage, exc.Message); - } - } - - - private static SqlException CreateLocalDBException(string errorMessage, uint sniError = 0) - { - Debug.Assert(!string.IsNullOrEmpty(errorMessage), "Error message should not be null or empty"); - - if (sniError != 0) - { - string sniErrorMessage = SQL.GetSNIErrorMessage(sniError); - errorMessage = $"{errorMessage} (error: {sniError} - {sniErrorMessage})"; - } - - SqlErrorCollection collection = new SqlErrorCollection - { - new SqlError( - infoNumber: (int)sniError, - errorState: 0, - errorClass: TdsEnums.FATAL_ERROR_CLASS, - server: null, - errorMessage, - procedure: null, - lineNumber: 0) - }; - - SqlException exc = SqlException.CreateException(collection, serverVersion: null); - exc._doNotReconnect = true; - - return exc; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs deleted file mode 100644 index e6b9a685dc..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using Interop.Windows.Kernel32; -using Microsoft.Data.SqlClient; -using Interop.Windows.Sni; - -namespace Microsoft.Data -{ - internal static partial class LocalDBAPI - { - private static IntPtr LoadProcAddress() => - Kernel32.GetProcAddress(UserInstanceDLLHandle, "LocalDBFormatMessage"); - - private static IntPtr UserInstanceDLLHandle - { - get - { - if (s_userInstanceDLLHandle == IntPtr.Zero) - { - lock (s_dllLock) - { - if (s_userInstanceDLLHandle == IntPtr.Zero) - { - SniNativeWrapper.SNIQueryInfo(QueryType.SNI_QUERY_LOCALDB_HMODULE, ref s_userInstanceDLLHandle); - if (s_userInstanceDLLHandle != IntPtr.Zero) - { - SqlClientEventSource.Log.TryTraceEvent("LocalDBAPI.UserInstanceDLLHandle | LocalDB - handle obtained"); - } - else - { - SniNativeWrapper.SNIGetLastError(out SniError sniError); - throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_FailedGetDLLHandle"), sniError: sniError.sniError); - } - } - } - } - return s_userInstanceDLLHandle; - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.cs deleted file mode 100644 index ba2371c232..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Runtime.InteropServices; -using System.Text; - -namespace Microsoft.Data -{ - internal static partial class LocalDBAPI - { - private const string LocalDbPrefix = @"(localdb)\"; - private const string LocalDbPrefix_NP = @"np:\\.\pipe\LOCALDB#"; - - - [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] - private delegate int LocalDBFormatMessageDelegate(int hrLocalDB, uint dwFlags, uint dwLanguageId, StringBuilder buffer, ref uint buflen); - - // check if name is in format (localdb)\ and return instance name if it is - // localDB can also have a format of np:\\.\pipe\LOCALDB#\tsql\query - internal static string GetLocalDbInstanceNameFromServerName(string serverName) - { - if (serverName is not null) - { - // it can start with spaces if specified in quotes - // Memory allocation is reduced by using ReadOnlySpan - ReadOnlySpan input = serverName.AsSpan().Trim(); - if (input.StartsWith(LocalDbPrefix.AsSpan(), StringComparison.OrdinalIgnoreCase)) - { - input = input.Slice(LocalDbPrefix.Length); - if (!input.IsEmpty) - { - return input.ToString(); - } - } - else if (input.StartsWith(LocalDbPrefix_NP.AsSpan(), StringComparison.OrdinalIgnoreCase)) - { - return input.ToString(); - } - - } - return null; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index 591977ef2d..bc05538a37 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -298,12 +298,30 @@ Microsoft\Data\ProviderBase\DbMetaDataFactory.cs + + Microsoft\Data\ProviderBase\DbReferenceCollection.cs + Microsoft\Data\ProviderBase\FieldNameLookup.cs Microsoft\Data\ProviderBase\TimeoutTimer.cs + + Microsoft\Data\Sql\SqlDataSourceEnumerator.Windows.cs + + + Microsoft\Data\Sql\SqlDataSourceEnumeratorNativeHelper.cs + + + Microsoft\Data\Sql\SqlDataSourceEnumeratorUtil.cs + + + Microsoft\Data\Sql\SqlNotificationRequest.cs + + + Resources\ResCategoryAttribute.cs + Microsoft\Data\SqlClient\SSPI\NativeSSPIContextProvider.cs @@ -319,18 +337,6 @@ Microsoft\Data\Sql\SqlDataSourceEnumerator.cs - - Microsoft\Data\Sql\SqlDataSourceEnumerator.Windows.cs - - - Microsoft\Data\Sql\SqlDataSourceEnumeratorNativeHelper.cs - - - Microsoft\Data\Sql\SqlDataSourceEnumeratorUtil.cs - - - Microsoft\Data\Sql\SqlNotificationRequest.cs - Microsoft\Data\SqlClient\AAsyncCallContext.cs @@ -382,6 +388,9 @@ Microsoft\Data\SqlClient\EnclaveSessionCache.cs + + Microsoft\Data\SqlClient\LocalDBAPI.Windows.cs + Microsoft\Data\SqlClient\LocalAppContextSwitches.cs @@ -793,6 +802,9 @@ Microsoft\Data\SqlClient\VirtualSecureModeEnclaveProviderBase.cs + + Microsoft\Data\SqlDbTypeExtensions.cs + Microsoft\Data\SqlTypes\SqlFileStream.cs @@ -805,18 +817,9 @@ Microsoft\Data\SqlTypes\SqlJson.cs - - Resources\ResCategoryAttribute.cs - Resources\ResDescriptionAttribute.cs - - Microsoft\Data\ProviderBase\DbReferenceCollection.cs - - - Microsoft\Data\SqlDbTypeExtensions.cs - System\IO\StreamExtensions.netfx.cs @@ -827,7 +830,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index c092b35a98..559b61c8af 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -6,6 +6,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Unix.cs similarity index 82% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Unix.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Unix.cs index ff85256c56..7555a80d32 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Unix.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Unix.cs @@ -8,6 +8,9 @@ namespace Microsoft.Data { internal static partial class LocalDBAPI { + internal static string GetLocalDbInstanceNameFromServerName(string serverName) => + null; + internal static string GetLocalDBMessage(int hrCode) => throw new PlatformNotSupportedException(Strings.LocalDBNotSupported); // LocalDB is not available for Unix and hence it cannot be supported. } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBAPI.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs similarity index 74% rename from src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBAPI.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs index 2d6192f6bf..77194052da 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBAPI.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs @@ -3,300 +3,167 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; -using System.Configuration; using System.Diagnostics; using System.Globalization; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security; using System.Text; -using System.Threading; -using Interop.Windows.Kernel32; using Interop.Windows.Sni; using Microsoft.Data.SqlClient; +using Interop.Windows.Kernel32; + +#if NETFRAMEWORK +using System.Collections.Generic; +using System.Configuration; +using System.Runtime.CompilerServices; +using System.Threading; +#endif namespace Microsoft.Data { internal static class LocalDBAPI { + private const int const_ErrorMessageBufferSize = 1024; // Buffer size for Local DB error message 1K will be enough for all messages + private const uint const_LOCALDB_TRUNCATE_ERR_MESSAGE = 1;// flag for LocalDBFormatMessage that indicates that message can be truncated if it does not fit in the buffer private const string LocalDbPrefix = @"(localdb)\"; private const string LocalDbPrefix_NP = @"np:\\.\pipe\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)\ and return instance name if it is - internal static string GetLocalDbInstanceNameFromServerName(string serverName) - { - if (serverName is not null) - { - // it can start with spaces if specified in quotes - // Memory allocation is reduced by using ReadOnlySpan - ReadOnlySpan input = serverName.AsSpan().Trim(); - if (input.StartsWith(LocalDbPrefix.AsSpan(), StringComparison.OrdinalIgnoreCase)) - { - input = input.Slice(LocalDbPrefix.Length); - if (!input.IsEmpty) - { - return input.ToString(); - } - } - else if (input.StartsWith(LocalDbPrefix_NP.AsSpan(), StringComparison.OrdinalIgnoreCase)) - { - return input.ToString(); - } - - } - return null; - } - - internal static void ReleaseDLLHandles() - { - s_userInstanceDLLHandle = IntPtr.Zero; - s_localDBFormatMessage = null; - s_localDBCreateInstance = null; - } - - - + + #if NETFRAMEWORK + private const string Const_partialTrustFlagKey = "ALLOW_LOCALDB_IN_PARTIAL_TRUST"; + #endif + + private static readonly object s_dllLock = new object(); + + #if NETFRAMEWORK + private static readonly object s_configLock = new object(); + #endif + + private static LocalDBFormatMessageDelegate s_localDBFormatMessage = 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) - { - SniNativeWrapper.SNIQueryInfo(QueryType.SNI_QUERY_LOCALDB_HMODULE, ref s_userInstanceDLLHandle); - if (s_userInstanceDLLHandle != IntPtr.Zero) - { - SqlClientEventSource.Log.TryTraceEvent(" LocalDB - handle obtained"); - } - else - { - SniError sniError = new SniError(); - SniNativeWrapper.SNIGetLastError(out sniError); - throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_FailedGetDLLHandle"), sniError: sniError.sniError); - } - } - } - finally - { - if (lockTaken) - Monitor.Exit(s_dllLock); - } - } - return s_userInstanceDLLHandle; - } - } - + private static IntPtr s_userInstanceDLLHandle = IntPtr.Zero; + + #if NETFRAMEWORK + private static PermissionSet _fullTrust = null; + private static bool _partialTrustFlagChecked = false; + private static bool _partialTrustAllowed = false; + private static Dictionary s_configurableInstances = null; + private static LocalDBCreateInstanceDelegate s_localDBCreateInstance = null; + #endif + + #if NETFRAMEWORK [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 + #endif + + [SuppressUnmanagedCodeSecurity] + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + private delegate int LocalDBFormatMessageDelegate(int hrLocalDB, uint dwFlags, uint dwLanguageId, StringBuilder buffer, ref uint buflen); + + #if NETFRAMEWORK + private static LocalDBCreateInstanceDelegate LocalDBCreateInstance { get { if (s_localDBCreateInstance == null) { - bool lockTaken = false; RuntimeHelpers.PrepareConstrainedRegions(); - try + + lock (s_dllLock) { - Monitor.Enter(s_dllLock, ref lockTaken); if (s_localDBCreateInstance == null) { - IntPtr functionAddr = Kernel32Safe.GetProcAddress(UserInstanceDLLHandle, "LocalDBCreateInstance"); - + IntPtr functionAddr = LoadProcAddress("LocalDBCreateInstance"); if (functionAddr == IntPtr.Zero) { int hResult = Marshal.GetLastWin32Error(); SqlClientEventSource.Log.TryTraceEvent(" GetProcAddress for LocalDBCreateInstance error 0x{0}", hResult); throw CreateLocalDBException(errorMessage: StringsHelper.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 + #endif + + private static LocalDBFormatMessageDelegate LocalDBFormatMessage { get { if (s_localDBFormatMessage == null) { - bool lockTaken = false; + #if NETFRAMEWORK RuntimeHelpers.PrepareConstrainedRegions(); - try + #endif + + lock (s_dllLock) { - Monitor.Enter(s_dllLock, ref lockTaken); if (s_localDBFormatMessage == null) { - IntPtr functionAddr = Kernel32Safe.GetProcAddress(UserInstanceDLLHandle, "LocalDBFormatMessage"); - + IntPtr functionAddr = LoadProcAddress("LocalDBFormatMessage"); if (functionAddr == IntPtr.Zero) { // SNI checks for LocalDBFormatMessage during DLL loading, so it is practically impossible to get this error. int hResult = Marshal.GetLastWin32Error(); - SqlClientEventSource.Log.TryTraceEvent(" GetProcAddress for LocalDBFormatMessage error 0x{0}", hResult); - throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_MethodNotFound")); + SqlClientEventSource.Log.TryTraceEvent("LocalDBAPI.LocalDBFormatMessage> GetProcAddress for LocalDBFormatMessage error 0x{0}", hResult); + throw CreateLocalDBException(errorMessage: Strings.LocalDB_MethodNotFound); } - s_localDBFormatMessage = (LocalDBFormatMessageDelegate)Marshal.GetDelegateForFunctionPointer(functionAddr, typeof(LocalDBFormatMessageDelegate)); + + s_localDBFormatMessage = Marshal.GetDelegateForFunctionPointer(functionAddr); } } - 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}).", StringsHelper.GetString("LocalDB_UnobtainableMessage"), hResult); - } - } - catch (SqlException exc) - { - return string.Format(CultureInfo.CurrentCulture, "{0} ({1}).", StringsHelper.GetString("LocalDB_UnobtainableMessage"), exc.Message); - } - } - - - static SqlException CreateLocalDBException(string errorMessage, string instance = null, int localDbError = 0, uint 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) ? (int)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 s_configurableInstances = null; - - internal static void DemandLocalDBPermissions() + + private static IntPtr UserInstanceDLLHandle { - if (!_partialTrustAllowed) + get { - if (!_partialTrustFlagChecked) + if (s_userInstanceDLLHandle == IntPtr.Zero) { - object partialTrustFlagValue = AppDomain.CurrentDomain.GetData(Const_partialTrustFlagKey); - if (partialTrustFlagValue != null && partialTrustFlagValue is bool) - { - _partialTrustAllowed = (bool)partialTrustFlagValue; - } - _partialTrustFlagChecked = true; - if (_partialTrustAllowed) + #if NETFRAMEWORK + RuntimeHelpers.PrepareConstrainedRegions(); + #endif + + lock (s_dllLock) { - return; + if (s_userInstanceDLLHandle == IntPtr.Zero) + { + SniNativeWrapper.SNIQueryInfo(QueryType.SNI_QUERY_LOCALDB_HMODULE, ref s_userInstanceDLLHandle); + if (s_userInstanceDLLHandle != IntPtr.Zero) + { + #if NETFRAMEWORK + SqlClientEventSource.Log.TryTraceEvent(" LocalDB - handle obtained"); + #else + SqlClientEventSource.Log.TryTraceEvent("LocalDBAPI.UserInstanceDLLHandle | LocalDB - handle obtained"); + #endif + } + else + { + SniNativeWrapper.SNIGetLastError(out SniError sniError); + throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_FailedGetDLLHandle"), sniError: sniError.sniError); + } + } } } - if (_fullTrust == null) - { - _fullTrust = new NamedPermissionSet("FullTrust"); - } - _fullTrust.Demand(); + return s_userInstanceDLLHandle; } } + #if NETFRAMEWORK internal static void AssertLocalDBPermissions() { _partialTrustAllowed = true; } - - + #endif + + #if NETFRAMEWORK internal static void CreateLocalDBInstance(string instance) { DemandLocalDBPermissions(); @@ -317,7 +184,10 @@ internal static void CreateLocalDBInstance(string instance) // validate section type LocalDBConfigurationSection configSection = section as LocalDBConfigurationSection; if (configSection == null) + { throw CreateLocalDBException(errorMessage: StringsHelper.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"); @@ -334,22 +204,32 @@ internal static void CreateLocalDBInstance(string instance) 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 + { + // instance name was not in the config + return; + } if (instanceInfo.created) - return; // instance has already been created + { + // instance has already been created + return; + } Debug.Assert(!instance.Contains("\0"), "Instance name should contain embedded nulls"); if (instanceInfo.version.Contains("\0")) + { throw CreateLocalDBException(errorMessage: StringsHelper.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); @@ -362,6 +242,157 @@ internal static void CreateLocalDBInstance(string instance) SqlClientEventSource.Log.TryTraceEvent(" Finished creation of instance {0}", instance); instanceInfo.created = true; // mark instance as created - } // CreateLocalDbInstance + } + #endif + + #if NETFRAMEWORK + 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(); + } + } + #endif + + // check if name is in format (localdb)\ and return instance name if it is + // localDB can also have a format of np:\\.\pipe\LOCALDB#\tsql\query + internal static string GetLocalDbInstanceNameFromServerName(string serverName) + { + if (serverName is not null) + { + // it can start with spaces if specified in quotes + // Memory allocation is reduced by using ReadOnlySpan + ReadOnlySpan input = serverName.AsSpan().Trim(); + if (input.StartsWith(LocalDbPrefix.AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + input = input.Slice(LocalDbPrefix.Length); + if (!input.IsEmpty) + { + return input.ToString(); + } + } + else if (input.StartsWith(LocalDbPrefix_NP.AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + return input.ToString(); + } + + } + return null; + } + + internal static string GetLocalDBMessage(int hrCode) + { + Debug.Assert(hrCode < 0, "HRCode does not indicate error"); + try + { + StringBuilder buffer = new StringBuilder((int)const_ErrorMessageBufferSize); + uint len = (uint)buffer.Capacity; + + // First try for current culture + int hResult = LocalDBFormatMessage(hrLocalDB: hrCode, dwFlags: const_LOCALDB_TRUNCATE_ERR_MESSAGE, dwLanguageId: (uint)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 = (uint)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}).", Strings.LocalDB_UnobtainableMessage, hResult); + } + } + } + catch (SqlException exc) + { + return string.Format(CultureInfo.CurrentCulture, "{0} ({1}).", Strings.LocalDB_UnobtainableMessage, exc.Message); + } + } + + internal static void ReleaseDLLHandles() + { + s_userInstanceDLLHandle = IntPtr.Zero; + s_localDBFormatMessage = null; + + #if NETFRAMEWORK + s_localDBCreateInstance = null; + #endif + } + + private static SqlException CreateLocalDBException(string errorMessage, string instance = null, int localDbError = 0, uint 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) ? (int)sniError : localDbError; + + if (sniError != 0) + { + string sniErrorMessage = SQL.GetSNIErrorMessage(sniError); + errorMessage = string.Format("{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 static IntPtr LoadProcAddress(string funcName) => + #if NETFRAMEWORK + Kernel32Safe.GetProcAddress(UserInstanceDLLHandle, funcName); + #else + Kernel32.GetProcAddress(UserInstanceDLLHandle, funcName); + #endif + + #if NETFRAMEWORK + private class InstanceInfo + { + internal InstanceInfo(string version) + { + this.version = version; + this.created = false; + } + + internal readonly string version; + internal bool created; + } + #endif } }