From 5649d5e6b13da00a2ef6d344c3a9f34d9ce25afd Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Thu, 14 Nov 2024 17:40:38 -0600 Subject: [PATCH 01/13] Move netcore LocalDBAPI.Common into LocalDBAPI.Windows since it is only loaded on Windows --- .../Data/SqlClient/LocalDBAPI.Common.cs | 124 ------------------ .../Data/SqlClient/LocalDBAPI.Windows.cs | 113 +++++++++++++++- 2 files changed, 112 insertions(+), 125 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Common.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 index e6b9a685dc..a099437022 100644 --- 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 @@ -3,14 +3,125 @@ // 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 Interop.Windows.Kernel32; -using Microsoft.Data.SqlClient; using Interop.Windows.Sni; +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, 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("{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() => Kernel32.GetProcAddress(UserInstanceDLLHandle, "LocalDBFormatMessage"); From 496a7cd58e13cc7dd1af817c5869a80924976a85 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Thu, 14 Nov 2024 17:57:13 -0600 Subject: [PATCH 02/13] Remove common file so we can just have unix and windows file. I do *not* want to try to decompose stuff in TdsParser to remove LocalDBAPI references on non-windows systems. --- .../src/Microsoft.Data.SqlClient.csproj | 3 +- .../Data/SqlClient/LocalDBAPI.Unix.cs | 3 ++ .../Data/SqlClient/LocalDBAPI.Windows.cs | 34 ++++++++++++++ .../Microsoft/Data/SqlClient/LocalDBAPI.cs | 46 ------------------- 4 files changed, 38 insertions(+), 48 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.cs 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..71f20f10dc 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 @@ - @@ -809,7 +808,7 @@ - + Microsoft\Data\Common\AdapterUtil.Unix.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Unix.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Unix.cs index ff85256c56..f0bfd8cb15 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Unix.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Unix.cs @@ -10,5 +10,8 @@ internal static partial class LocalDBAPI { internal static string GetLocalDBMessage(int hrCode) => throw new PlatformNotSupportedException(Strings.LocalDBNotSupported); // LocalDB is not available for Unix and hence it cannot be supported. + + internal static string GetLocalDbInstanceNameFromServerName(string serverName) => + null; } } 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 index a099437022..47881d522a 100644 --- 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 @@ -15,6 +15,40 @@ 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; + } + + private static LocalDBFormatMessageDelegate s_localDBFormatMessage = null; internal static void ReleaseDLLHandles() 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; - } - } -} From 1b13228552cc68c8b128228599b195f1fd8c202c Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Thu, 14 Nov 2024 18:15:19 -0600 Subject: [PATCH 03/13] Sort the members of LocalDBAPI --- .../src/Microsoft.Data.SqlClient.csproj | 2 +- .../Data/SqlClient/LocalDBAPI.Unix.cs | 6 +- .../Data/SqlClient/LocalDBAPI.Windows.cs | 142 ++++--- .../Microsoft/Data/SqlClient/LocalDBAPI.cs | 354 +++++++++--------- 4 files changed, 242 insertions(+), 262 deletions(-) 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 71f20f10dc..821bd942dd 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -808,7 +808,7 @@ - + Microsoft\Data\Common\AdapterUtil.Unix.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Unix.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Unix.cs index f0bfd8cb15..7555a80d32 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Unix.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Unix.cs @@ -8,10 +8,10 @@ namespace Microsoft.Data { internal static partial class LocalDBAPI { - internal static string GetLocalDBMessage(int hrCode) => - throw new PlatformNotSupportedException(Strings.LocalDBNotSupported); // LocalDB is not available for Unix and hence it cannot be supported. - 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/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs index 47881d522a..d4bc7d4b18 100644 --- 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 @@ -15,48 +15,19 @@ namespace Microsoft.Data { internal static partial class LocalDBAPI { + 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 private const string LocalDbPrefix = @"(localdb)\"; private const string LocalDbPrefix_NP = @"np:\\.\pipe\LOCALDB#"; + private static readonly object s_dllLock = new object(); - [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; - } - - 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 + private static IntPtr s_userInstanceDLLHandle = IntPtr.Zero; - internal static void ReleaseDLLHandles() - { - s_userInstanceDLLHandle = IntPtr.Zero; - s_localDBFormatMessage = null; - } - + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + private delegate int LocalDBFormatMessageDelegate(int hrLocalDB, uint dwFlags, uint dwLanguageId, StringBuilder buffer, ref uint buflen); private static LocalDBFormatMessageDelegate LocalDBFormatMessage { @@ -85,15 +56,58 @@ private static LocalDBFormatMessageDelegate 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 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: (int)sniError.sniError); + } + } + } + } + return s_userInstanceDLLHandle; + } + } - 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 + // 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) { @@ -106,7 +120,7 @@ internal static string GetLocalDBMessage(int hrCode) // 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); + buffer: buffer, buflen: ref len); if (hResult >= 0) return buffer.ToString(); else @@ -115,7 +129,7 @@ internal static string GetLocalDBMessage(int hrCode) 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); + buffer: buffer, buflen: ref len); if (hResult >= 0) return buffer.ToString(); else @@ -128,20 +142,25 @@ internal static string GetLocalDBMessage(int hrCode) } } + internal static void ReleaseDLLHandles() + { + s_userInstanceDLLHandle = IntPtr.Zero; + s_localDBFormatMessage = null; + } - private static SqlException CreateLocalDBException(string errorMessage, string instance = null, int localDbError = 0, int sniError = 0) + 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) ? sniError : localDbError; + 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); + errorMessage, sniError, sniErrorMessage); } collection.Add(new SqlError(errorCode, 0, TdsEnums.FATAL_ERROR_CLASS, instance, errorMessage, null, 0)); @@ -158,32 +177,5 @@ private static SqlException CreateLocalDBException(string errorMessage, string i 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/netfx/src/Microsoft/Data/SqlClient/LocalDBAPI.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBAPI.cs index 2d6192f6bf..349fee3d5b 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBAPI.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBAPI.cs @@ -20,97 +20,33 @@ namespace Microsoft.Data { internal static class LocalDBAPI { + private const int const_ErrorMessageBufferSize = 1024; // Buffer size for Local DB error message, according to Serverless team, 1K will be enough for all messages + private 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 + private const string Const_partialTrustFlagKey = "ALLOW_LOCALDB_IN_PARTIAL_TRUST"; 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; - } - + private static readonly object s_configLock = new object(); + private static readonly object s_dllLock = new object(); + 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; + 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; [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; + [SuppressUnmanagedCodeSecurity] + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + private delegate int LocalDBFormatMessageDelegate(int hrLocalDB, UInt32 dwFlags, UInt32 dwLanguageId, StringBuilder buffer, ref UInt32 buflen); - static LocalDBCreateInstanceDelegate LocalDBCreateInstance + private static LocalDBCreateInstanceDelegate LocalDBCreateInstance { get { @@ -144,14 +80,7 @@ static LocalDBCreateInstanceDelegate 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 + private static LocalDBFormatMessageDelegate LocalDBFormatMessage { get { @@ -186,108 +115,39 @@ static LocalDBFormatMessageDelegate 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) + private static IntPtr UserInstanceDLLHandle { - 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() - { - if (!_partialTrustAllowed) + get { - if (!_partialTrustFlagChecked) + if (s_userInstanceDLLHandle == IntPtr.Zero) { - object partialTrustFlagValue = AppDomain.CurrentDomain.GetData(Const_partialTrustFlagKey); - if (partialTrustFlagValue != null && partialTrustFlagValue is bool) + bool lockTaken = false; + RuntimeHelpers.PrepareConstrainedRegions(); + try { - _partialTrustAllowed = (bool)partialTrustFlagValue; + 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: (int)sniError.sniError); + } + } } - _partialTrustFlagChecked = true; - if (_partialTrustAllowed) + finally { - return; + if (lockTaken) + Monitor.Exit(s_dllLock); } } - if (_fullTrust == null) - { - _fullTrust = new NamedPermissionSet("FullTrust"); - } - _fullTrust.Demand(); + return s_userInstanceDLLHandle; } } @@ -296,7 +156,6 @@ internal static void AssertLocalDBPermissions() _partialTrustAllowed = true; } - internal static void CreateLocalDBInstance(string instance) { DemandLocalDBPermissions(); @@ -363,5 +222,134 @@ internal static void CreateLocalDBInstance(string instance) SqlClientEventSource.Log.TryTraceEvent(" Finished creation of instance {0}", instance); instanceInfo.created = true; // mark instance as created } // CreateLocalDbInstance + + 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(); + } + } + + // 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 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); + } + } + + internal static void ReleaseDLLHandles() + { + s_userInstanceDLLHandle = IntPtr.Zero; + s_localDBFormatMessage = null; + s_localDBCreateInstance = null; + } + + 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((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; + } } } From 1d8389ed7b9436a8b31591b94819aa81b6f4ace6 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Fri, 15 Nov 2024 17:42:35 -0600 Subject: [PATCH 04/13] Create merge file, merge LocalDBFormatMessage --- .../Data/SqlClient/LocalDBAPI.Windows.cs | 396 ++++++++++++++++++ 1 file changed, 396 insertions(+) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs new file mode 100644 index 0000000000..bb96a80404 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs @@ -0,0 +1,396 @@ +// 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.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; + +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 + //netfx private const string Const_partialTrustFlagKey = "ALLOW_LOCALDB_IN_PARTIAL_TRUST"; + private const string LocalDbPrefix = @"(localdb)\"; + private const string LocalDbPrefix_NP = @"np:\\.\pipe\LOCALDB#"; + + //netfx private static readonly object s_configLock = new object(); + private static readonly object s_dllLock = new object(); + + //netfx private static PermissionSet _fullTrust = null; + //netfx private static bool _partialTrustFlagChecked = false; + //netfx private static bool _partialTrustAllowed = false; + //netfx private static Dictionary s_configurableInstances = null; + //netfx private static LocalDBCreateInstanceDelegate s_localDBCreateInstance = null; + 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 + private static IntPtr s_userInstanceDLLHandle = IntPtr.Zero; + + //netfx--- + [SuppressUnmanagedCodeSecurity] + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate int LocalDBCreateInstanceDelegate([MarshalAs(UnmanagedType.LPWStr)] string version, [MarshalAs(UnmanagedType.LPWStr)] string instance, UInt32 flags); + //---netfx + + [SuppressUnmanagedCodeSecurity] + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + private delegate int LocalDBFormatMessageDelegate(int hrLocalDB, uint dwFlags, uint dwLanguageId, StringBuilder buffer, ref uint buflen); + + //netfx--- + private 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 = Kernel32Safe.GetProcAddress(UserInstanceDLLHandle, "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; + } + } + //---netfx + + private static LocalDBFormatMessageDelegate LocalDBFormatMessage + { + get + { + if (s_localDBFormatMessage == null) + { + #if NETFRAMEWORK + RuntimeHelpers.PrepareConstrainedRegions(); + #endif + + 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; + } + } + + //netcore--- + 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: (int)sniError.sniError); + } + } + } + } + return s_userInstanceDLLHandle; + } + } + //---netcore + //netfx--- + private 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: (int)sniError.sniError); + } + } + } + finally + { + if (lockTaken) + Monitor.Exit(s_dllLock); + } + } + return s_userInstanceDLLHandle; + } + } + //---netfx + + //netfx--- + 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 tempConfigurableInstances = new Dictionary(StringComparer.OrdinalIgnoreCase); + object section = ConfigurationManager.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: 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"); + tempConfigurableInstances.Add(confElement.Name.Trim(), new InstanceInfo(confElement.Version.Trim())); + } + } + else + { + SqlClientEventSource.Log.TryTraceEvent(" 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: 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); + SqlClientEventSource.Log.TryTraceEvent(" Starting creation of instance {0} version {1}", instance, instanceInfo.version); + + if (hr < 0) + { + throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_CreateFailed"), instance: instance, localDbError: hr); + } + + SqlClientEventSource.Log.TryTraceEvent(" Finished creation of instance {0}", instance); + instanceInfo.created = true; // mark instance as created + } // CreateLocalDbInstance + + 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(); + } + } + //---netfx + + // 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 + //netfx return string.Format(CultureInfo.CurrentCulture, "{0} (0x{1:X}).", StringsHelper.GetString("LocalDB_UnobtainableMessage"), hResult); + //netcore return string.Format(CultureInfo.CurrentCulture, "{0} (0x{1:X}).", Strings.LocalDB_UnobtainableMessage, hResult); + } + } + catch (SqlException exc) + { + //netfx return string.Format(CultureInfo.CurrentCulture, "{0} ({1}).", StringsHelper.GetString("LocalDB_UnobtainableMessage"), exc.Message); + //netcore return string.Format(CultureInfo.CurrentCulture, "{0} ({1}).", Strings.LocalDB_UnobtainableMessage, exc.Message); + } + } + + internal static void ReleaseDLLHandles() + { + s_userInstanceDLLHandle = IntPtr.Zero; + s_localDBFormatMessage = null; + //netfx s_localDBCreateInstance = null; + } + + private 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); + //netfx errorMessage = String.Format((IFormatProvider)null, "{0} (error: {1} - {2})", + //netcore 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() => + Kernel32Safe.GetProcAddress(UserInstanceDLLHandle, "LocalDBFormatMessage"); + + //netfx--- + private class InstanceInfo + { + internal InstanceInfo(string version) + { + this.version = version; + this.created = false; + } + + internal readonly string version; + internal bool created; + } + //---netfx + } +} From aa26bcdf9408fe3813643226572a1b7b5d64e692 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Fri, 15 Nov 2024 17:46:29 -0600 Subject: [PATCH 05/13] Merge member variables and delegates --- .../Data/SqlClient/LocalDBAPI.Windows.cs | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs index bb96a80404..5ed18b4e96 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs @@ -22,29 +22,40 @@ 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 - //netfx private const string Const_partialTrustFlagKey = "ALLOW_LOCALDB_IN_PARTIAL_TRUST"; private const string LocalDbPrefix = @"(localdb)\"; private const string LocalDbPrefix_NP = @"np:\\.\pipe\LOCALDB#"; - - //netfx private static readonly object s_configLock = new object(); + + #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 - //netfx private static PermissionSet _fullTrust = null; - //netfx private static bool _partialTrustFlagChecked = false; - //netfx private static bool _partialTrustAllowed = false; - //netfx private static Dictionary s_configurableInstances = null; - //netfx private static LocalDBCreateInstanceDelegate s_localDBCreateInstance = null; 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 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 - //netfx--- + #if NETFRAMEWORK [SuppressUnmanagedCodeSecurity] [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int LocalDBCreateInstanceDelegate([MarshalAs(UnmanagedType.LPWStr)] string version, [MarshalAs(UnmanagedType.LPWStr)] string instance, UInt32 flags); - //---netfx + #endif + #if NETFRAMEWORK [SuppressUnmanagedCodeSecurity] + #endif [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] private delegate int LocalDBFormatMessageDelegate(int hrLocalDB, uint dwFlags, uint dwLanguageId, StringBuilder buffer, ref uint buflen); From 0cd53ed97f41f0ac1be7d07df4636732a76acbc4 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Fri, 15 Nov 2024 17:51:26 -0600 Subject: [PATCH 06/13] Merge LocalDBCreateInstanceDelegate (and update to use locks over monitors) --- .../Data/SqlClient/LocalDBAPI.Windows.cs | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs index 5ed18b4e96..84728b89dc 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs @@ -53,47 +53,39 @@ internal static class LocalDBAPI private delegate int LocalDBCreateInstanceDelegate([MarshalAs(UnmanagedType.LPWStr)] string version, [MarshalAs(UnmanagedType.LPWStr)] string instance, UInt32 flags); #endif - #if NETFRAMEWORK [SuppressUnmanagedCodeSecurity] - #endif [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] private delegate int LocalDBFormatMessageDelegate(int hrLocalDB, uint dwFlags, uint dwLanguageId, StringBuilder buffer, ref uint buflen); - //netfx--- + #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(); 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; } } - //---netfx + #endif private static LocalDBFormatMessageDelegate LocalDBFormatMessage { From 2bdf992027d60aa3901aac2c91657f380ef9a507 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Fri, 15 Nov 2024 17:54:45 -0600 Subject: [PATCH 07/13] Merge UserInstanceDLLHandle --- .../Data/SqlClient/LocalDBAPI.Windows.cs | 47 ++++--------------- 1 file changed, 8 insertions(+), 39 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs index 84728b89dc..629fe90201 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs @@ -118,13 +118,16 @@ private static LocalDBFormatMessageDelegate LocalDBFormatMessage } } - //netcore--- private static IntPtr UserInstanceDLLHandle { get { if (s_userInstanceDLLHandle == IntPtr.Zero) { + #if NETFRAMEWORK + RuntimeHelpers.PrepareConstrainedRegions(); + #endif + lock (s_dllLock) { if (s_userInstanceDLLHandle == IntPtr.Zero) @@ -132,7 +135,11 @@ private static IntPtr UserInstanceDLLHandle 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 { @@ -145,44 +152,6 @@ private static IntPtr UserInstanceDLLHandle return s_userInstanceDLLHandle; } } - //---netcore - //netfx--- - private 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: (int)sniError.sniError); - } - } - } - finally - { - if (lockTaken) - Monitor.Exit(s_dllLock); - } - } - return s_userInstanceDLLHandle; - } - } - //---netfx //netfx--- internal static void AssertLocalDBPermissions() From 28699d91f834373955163036c9f34b69fb10f6f7 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Fri, 15 Nov 2024 18:09:20 -0600 Subject: [PATCH 08/13] Fixing issue with Kernel32 reference --- .../Data/SqlClient/LocalDBAPI.Windows.cs | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs index 629fe90201..4549db2ec5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs @@ -3,18 +3,21 @@ // 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 { @@ -153,12 +156,14 @@ private static IntPtr UserInstanceDLLHandle } } - //netfx--- + #if NETFRAMEWORK internal static void AssertLocalDBPermissions() { _partialTrustAllowed = true; } + #endif + #if NETFRAMEWORK internal static void CreateLocalDBInstance(string instance) { DemandLocalDBPermissions(); @@ -224,8 +229,10 @@ 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) @@ -250,7 +257,7 @@ internal static void DemandLocalDBPermissions() _fullTrust.Demand(); } } - //---netfx + #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 @@ -286,7 +293,6 @@ internal static string GetLocalDBMessage(int hrCode) 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); @@ -302,14 +308,12 @@ internal static string GetLocalDBMessage(int hrCode) if (hResult >= 0) return buffer.ToString(); else - //netfx return string.Format(CultureInfo.CurrentCulture, "{0} (0x{1:X}).", StringsHelper.GetString("LocalDB_UnobtainableMessage"), hResult); - //netcore return string.Format(CultureInfo.CurrentCulture, "{0} (0x{1:X}).", Strings.LocalDB_UnobtainableMessage, hResult); + return string.Format(CultureInfo.CurrentCulture, "{0} (0x{1:X}).", Strings.LocalDB_UnobtainableMessage, hResult); } } catch (SqlException exc) { - //netfx return string.Format(CultureInfo.CurrentCulture, "{0} ({1}).", StringsHelper.GetString("LocalDB_UnobtainableMessage"), exc.Message); - //netcore return string.Format(CultureInfo.CurrentCulture, "{0} ({1}).", Strings.LocalDB_UnobtainableMessage, exc.Message); + return string.Format(CultureInfo.CurrentCulture, "{0} ({1}).", Strings.LocalDB_UnobtainableMessage, exc.Message); } } @@ -317,7 +321,10 @@ internal static void ReleaseDLLHandles() { s_userInstanceDLLHandle = IntPtr.Zero; s_localDBFormatMessage = null; - //netfx s_localDBCreateInstance = null; + + #if NETFRAMEWORK + s_localDBCreateInstance = null; + #endif } private static SqlException CreateLocalDBException(string errorMessage, string instance = null, int localDbError = 0, int sniError = 0) @@ -331,9 +338,7 @@ private static SqlException CreateLocalDBException(string errorMessage, string i if (sniError != 0) { string sniErrorMessage = SQL.GetSNIErrorMessage(sniError); - //netfx errorMessage = String.Format((IFormatProvider)null, "{0} (error: {1} - {2})", - //netcore errorMessage = string.Format("{0} (error: {1} - {2})", - errorMessage, sniError, sniErrorMessage); + errorMessage = string.Format("{0} (error: {1} - {2})", errorMessage, sniError, sniErrorMessage); } collection.Add(new SqlError(errorCode, 0, TdsEnums.FATAL_ERROR_CLASS, instance, errorMessage, null, 0)); @@ -349,9 +354,13 @@ private static SqlException CreateLocalDBException(string errorMessage, string i } private static IntPtr LoadProcAddress() => + #if NETFRAMEWORK Kernel32Safe.GetProcAddress(UserInstanceDLLHandle, "LocalDBFormatMessage"); + #else + Kernel32.GetProcAddress(UserInstanceDLLHandle, "LocalDBFormatMessage"); + #endif - //netfx--- + #if NETFRAMEWORK private class InstanceInfo { internal InstanceInfo(string version) @@ -363,6 +372,6 @@ internal InstanceInfo(string version) internal readonly string version; internal bool created; } - //---netfx + #endif } } From 3b677f1dea13cbd9600c51f4c5caca6b8d0c2307 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Fri, 15 Nov 2024 18:12:15 -0600 Subject: [PATCH 09/13] Removing old files and adding merged file to projects --- .../src/Microsoft.Data.SqlClient.csproj | 7 +- .../Data/SqlClient/LocalDBAPI.Windows.cs | 181 --------- .../netfx/src/Microsoft.Data.SqlClient.csproj | 46 +-- .../Microsoft/Data/SqlClient/LocalDBAPI.cs | 355 ------------------ 4 files changed, 28 insertions(+), 561 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs delete mode 100644 src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBAPI.cs 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 821bd942dd..3b601d3f9d 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -778,6 +778,9 @@ Microsoft\Data\Sql\SqlDataSourceEnumerator.Windows.cs + + Microsoft\Data\SqlClient\LocalDBAPI.Windows.cs + Microsoft\Data\SqlClient\SqlColumnEncryptionCngProvider.Windows.cs @@ -796,9 +799,7 @@ Microsoft\Data\SqlTypes\SqlFileStream.Windows.cs - - - + 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 d4bc7d4b18..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs +++ /dev/null @@ -1,181 +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 Interop.Windows.Kernel32; -using Interop.Windows.Sni; -using Microsoft.Data.SqlClient; - -namespace Microsoft.Data -{ - internal static partial class LocalDBAPI - { - 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 - private const string LocalDbPrefix = @"(localdb)\"; - private const string LocalDbPrefix_NP = @"np:\\.\pipe\LOCALDB#"; - - private static readonly object s_dllLock = new object(); - - 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 - private static IntPtr s_userInstanceDLLHandle = IntPtr.Zero; - - [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] - private delegate int LocalDBFormatMessageDelegate(int hrLocalDB, uint dwFlags, uint dwLanguageId, StringBuilder buffer, ref uint buflen); - - 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; - } - } - - 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: (int)sniError.sniError); - } - } - } - } - return s_userInstanceDLLHandle; - } - } - - // 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; - } - - 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() => - Kernel32.GetProcAddress(UserInstanceDLLHandle, "LocalDBFormatMessage"); - } -} 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/netfx/src/Microsoft/Data/SqlClient/LocalDBAPI.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBAPI.cs deleted file mode 100644 index 349fee3d5b..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBAPI.cs +++ /dev/null @@ -1,355 +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.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; - -namespace Microsoft.Data -{ - internal static class LocalDBAPI - { - private const int const_ErrorMessageBufferSize = 1024; // Buffer size for Local DB error message, according to Serverless team, 1K will be enough for all messages - private 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 - private const string Const_partialTrustFlagKey = "ALLOW_LOCALDB_IN_PARTIAL_TRUST"; - private const string LocalDbPrefix = @"(localdb)\"; - private const string LocalDbPrefix_NP = @"np:\\.\pipe\LOCALDB#"; - - private static readonly object s_configLock = new object(); - private static readonly object s_dllLock = new object(); - - 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; - 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 - private static IntPtr s_userInstanceDLLHandle = IntPtr.Zero; - - [SuppressUnmanagedCodeSecurity] - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate int LocalDBCreateInstanceDelegate([MarshalAs(UnmanagedType.LPWStr)] string version, [MarshalAs(UnmanagedType.LPWStr)] string instance, UInt32 flags); - - [SuppressUnmanagedCodeSecurity] - [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] - private delegate int LocalDBFormatMessageDelegate(int hrLocalDB, UInt32 dwFlags, UInt32 dwLanguageId, StringBuilder buffer, ref UInt32 buflen); - - private 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 = Kernel32Safe.GetProcAddress(UserInstanceDLLHandle, "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; - } - } - - private 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 = Kernel32Safe.GetProcAddress(UserInstanceDLLHandle, "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")); - } - s_localDBFormatMessage = (LocalDBFormatMessageDelegate)Marshal.GetDelegateForFunctionPointer(functionAddr, typeof(LocalDBFormatMessageDelegate)); - } - } - finally - { - if (lockTaken) - Monitor.Exit(s_dllLock); - } - } - return s_localDBFormatMessage; - } - } - - private 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: (int)sniError.sniError); - } - } - } - finally - { - if (lockTaken) - Monitor.Exit(s_dllLock); - } - } - return s_userInstanceDLLHandle; - } - } - - 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 tempConfigurableInstances = new Dictionary(StringComparer.OrdinalIgnoreCase); - object section = ConfigurationManager.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: 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"); - tempConfigurableInstances.Add(confElement.Name.Trim(), new InstanceInfo(confElement.Version.Trim())); - } - } - else - { - SqlClientEventSource.Log.TryTraceEvent(" 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: 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); - SqlClientEventSource.Log.TryTraceEvent(" Starting creation of instance {0} version {1}", instance, instanceInfo.version); - - if (hr < 0) - { - throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_CreateFailed"), instance: instance, localDbError: hr); - } - - SqlClientEventSource.Log.TryTraceEvent(" Finished creation of instance {0}", instance); - instanceInfo.created = true; // mark instance as created - } // CreateLocalDbInstance - - 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(); - } - } - - // 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 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); - } - } - - internal static void ReleaseDLLHandles() - { - s_userInstanceDLLHandle = IntPtr.Zero; - s_localDBFormatMessage = null; - s_localDBCreateInstance = null; - } - - 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((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; - } - } -} From 4f5751ca8867aa4860df33dcb10e650439be57e9 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Tue, 19 Nov 2024 17:48:49 -0600 Subject: [PATCH 10/13] Patching things up after rebase --- .../src/Microsoft.Data.SqlClient.csproj | 1 + .../src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) 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/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs index 4549db2ec5..a68f82d2b5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs @@ -135,7 +135,7 @@ private static IntPtr UserInstanceDLLHandle { if (s_userInstanceDLLHandle == IntPtr.Zero) { - SniNativeWrapper.SniQueryInfo(QueryType.SNI_QUERY_LOCALDB_HMODULE, ref s_userInstanceDLLHandle); + SniNativeWrapper.SNIQueryInfo(QueryType.SNI_QUERY_LOCALDB_HMODULE, ref s_userInstanceDLLHandle); if (s_userInstanceDLLHandle != IntPtr.Zero) { #if NETFRAMEWORK @@ -146,8 +146,8 @@ private static IntPtr UserInstanceDLLHandle } else { - SniNativeWrapper.SniGetLastError(out SniError sniError); - throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_FailedGetDLLHandle"), sniError: (int)sniError.sniError); + SniNativeWrapper.SNIGetLastError(out SniError sniError); + throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_FailedGetDLLHandle"), sniError: sniError.sniError); } } } @@ -327,13 +327,13 @@ internal static void ReleaseDLLHandles() #endif } - private static SqlException CreateLocalDBException(string errorMessage, string instance = null, int localDbError = 0, int sniError = 0) + 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) ? sniError : localDbError; + int errorCode = (localDbError == 0) ? (int)sniError : localDbError; if (sniError != 0) { From 8d087cac846ffe1115004d00c2a2c8c3b2981e82 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Wed, 27 Nov 2024 13:15:06 -0600 Subject: [PATCH 11/13] Move Unix code to common project --- .../netcore/src/Microsoft.Data.SqlClient.csproj | 4 +++- .../src/Microsoft/Data/SqlClient/LocalDBAPI.Unix.cs | 0 2 files changed, 3 insertions(+), 1 deletion(-) rename src/Microsoft.Data.SqlClient/{netcore => }/src/Microsoft/Data/SqlClient/LocalDBAPI.Unix.cs (100%) 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 3b601d3f9d..7f30cb6ab3 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -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.Unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Unix.cs similarity index 100% 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 From 514146cbd1ab8a32136740150bf02f565c89eac0 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Wed, 27 Nov 2024 16:53:24 -0600 Subject: [PATCH 12/13] Add braces for if statements --- .../Data/SqlClient/LocalDBAPI.Windows.cs | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs index a68f82d2b5..7337fea164 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs @@ -184,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"); @@ -201,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); @@ -297,7 +310,9 @@ internal static string GetLocalDBMessage(int hrCode) 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 @@ -306,9 +321,13 @@ internal static string GetLocalDBMessage(int hrCode) 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) @@ -344,7 +363,9 @@ private static SqlException CreateLocalDBException(string errorMessage, string i 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)); + { + collection.Add(new SqlError(errorCode, 0, TdsEnums.FATAL_ERROR_CLASS, instance, GetLocalDBMessage(localDbError), null, 0)); + } SqlException exc = SqlException.CreateException(collection, null); From 795d7524cbb04e7344d8227f649370397150d064 Mon Sep 17 00:00:00 2001 From: Ben Russell Date: Wed, 27 Nov 2024 17:16:27 -0600 Subject: [PATCH 13/13] Fixing hardcoded func name in LoadProcAddress --- .../src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs index 7337fea164..77194052da 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs @@ -73,7 +73,7 @@ private static LocalDBCreateInstanceDelegate LocalDBCreateInstance { if (s_localDBCreateInstance == null) { - IntPtr functionAddr = LoadProcAddress(); + IntPtr functionAddr = LoadProcAddress("LocalDBCreateInstance"); if (functionAddr == IntPtr.Zero) { int hResult = Marshal.GetLastWin32Error(); @@ -104,7 +104,7 @@ private static LocalDBFormatMessageDelegate LocalDBFormatMessage { if (s_localDBFormatMessage == null) { - IntPtr functionAddr = LoadProcAddress(); + IntPtr functionAddr = LoadProcAddress("LocalDBFormatMessage"); if (functionAddr == IntPtr.Zero) { // SNI checks for LocalDBFormatMessage during DLL loading, so it is practically impossible to get this error. @@ -374,11 +374,11 @@ private static SqlException CreateLocalDBException(string errorMessage, string i return exc; } - private static IntPtr LoadProcAddress() => + private static IntPtr LoadProcAddress(string funcName) => #if NETFRAMEWORK - Kernel32Safe.GetProcAddress(UserInstanceDLLHandle, "LocalDBFormatMessage"); + Kernel32Safe.GetProcAddress(UserInstanceDLLHandle, funcName); #else - Kernel32.GetProcAddress(UserInstanceDLLHandle, "LocalDBFormatMessage"); + Kernel32.GetProcAddress(UserInstanceDLLHandle, funcName); #endif #if NETFRAMEWORK