From c1cc8d1ca31104c02770f712c212d2f2212683c2 Mon Sep 17 00:00:00 2001 From: Kaur-Parminder Date: Wed, 8 Dec 2021 13:58:27 -0800 Subject: [PATCH 1/3] Porting SqlDataSourceEnumerater feature from System.Data.Sql Porting SqlDataSourceEnumerater feature from System.Data.Sql --- .../Interop/SNINativeMethodWrapper.Windows.cs | 11 +- .../src/Microsoft.Data.SqlClient.csproj | 3 + .../Data/SqlClient/TdsParserStaticMethods.cs | 4 + .../netfx/src/Microsoft.Data.SqlClient.csproj | 3 + .../Interop/SNINativeManagedWrapperX64.cs | 9 + .../Data/Interop/SNINativeMethodWrapper.cs | 32 +++ .../Data/SqlClient/TdsParserStaticMethods.cs | 5 + .../src/Microsoft/Data/Common/AdapterUtil.cs | 5 +- .../Data/Sql/SqlDataSourceEnumerator.cs | 198 ++++++++++++++++++ .../Microsoft.Data.SqlClient.Tests.csproj | 2 + .../SqlDataSourceEnumeratorTest.cs | 21 ++ 11 files changed, 290 insertions(+), 3 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlDataSourceEnumeratorTest.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs index 7f1b3e17ea..382c58333e 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs @@ -306,7 +306,16 @@ private static extern unsafe uint SNISecGenClientContextWrapper( [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] private static extern uint SNIWriteSyncOverAsync(SNIHandle pConn, [In] SNIPacket pPacket); - #endregion + + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumOpenWrapper")] + internal static extern IntPtr SNIServerEnumOpen(); + + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumCloseWrapper")] + internal static extern void SNIServerEnumClose([In] IntPtr packet); + + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumReadWrapper")] + internal static extern int SNIServerEnumRead([In] IntPtr packet, [In, Out] char[] readBuffer, int bufferLength, ref bool more); + #endregion internal static uint SniGetConnectionId(SNIHandle pConn, ref Guid connId) { 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 5b808d9b78..50e66a928e 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -43,6 +43,9 @@ Microsoft\Data\Sql\SqlNotificationRequest.cs + + Microsoft\Data\Sql\SqlDataSourceEnumerator.cs + Microsoft\Data\Common\ActivityCorrelator.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs index e17a65be95..ab3efb87b4 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs @@ -118,6 +118,10 @@ internal static int GetTimeoutMilliseconds(long timeoutTime) } return (int)msecRemaining; } + static internal long GetTimeoutSeconds(int timeout) + { + return GetTimeout((long)timeout * 1000L); + } internal static long GetTimeout(long timeoutMilliseconds) { 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 df8e73b2d3..7d9213c95a 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -119,6 +119,9 @@ Microsoft\Data\Sql\SqlNotificationRequest.cs + + Microsoft\Data\Sql\SqlDataSourceEnumerator.cs + Microsoft\Data\SqlClient\DataClassification\SensitivityClassification.cs diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs index abbfda7ede..d851c01eda 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs @@ -134,5 +134,14 @@ internal static extern unsafe uint SNISecGenClientContextWrapper( [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr SNIClientCertificateFallbackWrapper(IntPtr pCallbackContext); + + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumOpenWrapper")] + internal static extern IntPtr SNIServerEnumOpen(); + + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumCloseWrapper")] + internal static extern void SNIServerEnumClose([In] IntPtr packet); + + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumReadWrapper")] + internal static extern int SNIServerEnumRead([In] IntPtr packet, [In, Out] char[] readBuffer, int bufferLength, out bool more); } } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs index d1fb0ad3e5..b8fe3f3195 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs @@ -760,6 +760,38 @@ internal static uint SNIInitialize() return SNIInitialize(LocalAppContextSwitches.UseSystemDefaultSecureProtocols, IntPtr.Zero); } + internal static IntPtr SNIServerEnumOpen() + { + if (s_is64bitProcess) + { + return SNINativeManagedWrapperX64.SNIServerEnumOpen(); + } + else + { + return SNINativeManagedWrapperX86.SNIServerEnumOpen(); + } + } + + internal static void SNIServerEnumClose([In] IntPtr packet) + { + if (s_is64bitProcess) + { + SNINativeManagedWrapperX64.SNIServerEnumClose(packet); + } + else + { + SNINativeManagedWrapperX86.SNIServerEnumClose(packet); + } + + } + + internal static int SNIServerEnumRead([In] IntPtr packet, [In, Out] char[] readbuffer, int bufferLength, ref bool more) + { + return s_is64bitProcess ? + SNINativeManagedWrapperX64.SNIServerEnumRead(packet, readbuffer, bufferLength, out more) : + SNINativeManagedWrapperX86.SNIServerEnumRead(packet, readbuffer, bufferLength, out more); + } + internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHandle parent, ref IntPtr pConn, bool fSync, SqlConnectionIPAddressPreference ipPreference, SQLDNSInfo cachedDNSInfo) { // initialize consumer info for MARS diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs index 025ea7d020..0241220560 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs @@ -221,6 +221,11 @@ internal static long GetTimeout(long timeoutMilliseconds) return result; } + static internal long GetTimeoutSeconds(int timeout) + { + return GetTimeout((long)timeout * 1000L); + } + internal static bool TimeoutHasExpired(long timeoutTime) { bool result = false; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs index f7e3715ccc..4fb2045326 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -1201,6 +1201,9 @@ static internal Exception InvalidMixedUsageOfCredentialAndAccessToken() => InvalidOperation(StringsHelper.GetString(Strings.ADP_InvalidMixedUsageOfCredentialAndAccessToken)); #endregion + internal static bool IsEmpty(string str) => string.IsNullOrEmpty(str); + internal static readonly IntPtr s_ptrZero = IntPtr.Zero; // IntPtr.Zero + #if NETFRAMEWORK #region netfx project only internal static Task CreatedTaskWithException(Exception ex) @@ -1355,7 +1358,6 @@ internal static InvalidOperationException ComputerNameEx(int lastError) internal const float FailoverTimeoutStepForTnir = 0.125F; // Fraction of timeout to use in case of Transparent Network IP resolution. internal const int MinimumTimeoutForTnirMs = 500; // The first login attempt in Transparent network IP Resolution - internal static readonly IntPtr s_ptrZero = IntPtr.Zero; // IntPtr.Zero internal static readonly int s_ptrSize = IntPtr.Size; internal static readonly IntPtr s_invalidPtr = new(-1); // use for INVALID_HANDLE @@ -1446,7 +1448,6 @@ internal static IntPtr IntPtrOffset(IntPtr pbase, int offset) return (IntPtr)checked(pbase.ToInt64() + offset); } - internal static bool IsEmpty(string str) => string.IsNullOrEmpty(str); #endregion #else #region netcore project only diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.cs new file mode 100644 index 0000000000..0a25fe45b7 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.cs @@ -0,0 +1,198 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// Microsoft +// Microsoft +//------------------------------------------------------------------------------ +using Microsoft.Data.Common; +using Microsoft.Data.SqlClient; + +using System; +using System.Data; +using System.Data.Common; +using System.Diagnostics; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Text; +using System.Text.RegularExpressions; + +namespace Microsoft.Data.Sql +{ + /// + /// Provides a mechanism for enumerating all available instances of SQL Server within the local network + /// + public sealed class SqlDataSourceEnumerator : DbDataSourceEnumerator + { + + private static readonly SqlDataSourceEnumerator SingletonInstance = new SqlDataSourceEnumerator(); + internal const string ServerName = "ServerName"; + internal const string InstanceName = "InstanceName"; + internal const string IsClustered = "IsClustered"; + internal const string Version = "Version"; + private static string _Version = "Version:"; + private static string _Cluster = "Clustered:"; + private static int _clusterLength = _Cluster.Length; + private static int _versionLength = _Version.Length; + private const int timeoutSeconds = ADP.DefaultCommandTimeout; + private long timeoutTime; // variable used for timeout computations, holds the value of the hi-res performance counter at which this request should expire + + private SqlDataSourceEnumerator() : base() + { + } + + /// + /// Gets an instance of the SqlDataSourceEnumerator, which can be used to retrieve information about available SQL Server instances. + /// + public static SqlDataSourceEnumerator Instance => SqlDataSourceEnumerator.SingletonInstance; + + /// + /// Retrieves a DataTable containing information about all visible SQL Server instances + /// + /// + override public DataTable GetDataSources() + { + (new NamedPermissionSet("FullTrust")).Demand(); // SQLBUDT 244304 + char[] buffer = null; + StringBuilder strbldr = new StringBuilder(); + + Int32 bufferSize = 1024; + Int32 readLength = 0; + buffer = new char[bufferSize]; + bool more = true; + bool failure = false; + IntPtr handle = ADP.s_ptrZero; + + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + timeoutTime = TdsParserStaticMethods.GetTimeoutSeconds(timeoutSeconds); + RuntimeHelpers.PrepareConstrainedRegions(); + try + { } + finally + { + handle = SNINativeMethodWrapper.SNIServerEnumOpen(); + } + + if (handle != ADP.s_ptrZero) + { + while (more && !TdsParserStaticMethods.TimeoutHasExpired(timeoutTime)) + { + readLength = SNINativeMethodWrapper.SNIServerEnumRead(handle, buffer, bufferSize, ref more); + + if (readLength > bufferSize) + { + failure = true; + more = false; + } + else if (0 < readLength) + { + strbldr.Append(buffer, 0, readLength); + } + } + } + } + finally + { + if (handle != ADP.s_ptrZero) + { + SNINativeMethodWrapper.SNIServerEnumClose(handle); + } + } + + if (failure) + { + Debug.Assert(false, "GetDataSources:SNIServerEnumRead returned bad length"); + SqlClientEventSource.Log.TryTraceEvent(" GetDataSources:SNIServerEnumRead returned bad length, requested %d, received %d", bufferSize, readLength); + throw ADP.ArgumentOutOfRange("readLength"); + } + + return ParseServerEnumString(strbldr.ToString()); + } + + static private System.Data.DataTable ParseServerEnumString(string serverInstances) + { + DataTable dataTable = new DataTable("SqlDataSources"); + dataTable.Locale = CultureInfo.InvariantCulture; + dataTable.Columns.Add(ServerName, typeof(string)); + dataTable.Columns.Add(InstanceName, typeof(string)); + dataTable.Columns.Add(IsClustered, typeof(string)); + dataTable.Columns.Add(Version, typeof(string)); + DataRow dataRow = null; + string serverName = null; + string instanceName = null; + string isClustered = null; + string version = null; + + // Every row comes in the format "serverName\instanceName;Clustered:[Yes|No];Version:.." + // Every row is terminated by a null character. + // Process one row at a time + foreach (string instance in serverInstances.Split(new string[] { "\0\0\0" }, StringSplitOptions.None)) + { + // string value = instance.Trim('\0'); // MDAC 91934 + string value = instance.Replace("\0", ""); + if (0 == value.Length) + { + continue; + } + foreach (string instance2 in value.Split(';')) + { + if (serverName == null) + { + foreach (string instance3 in instance2.Split('\\')) + { + if (serverName == null) + { + serverName = instance3; + continue; + } + Debug.Assert(instanceName == null); + instanceName = instance3; + } + continue; + } + if (isClustered == null) + { + Debug.Assert(String.Compare(_Cluster, 0, instance2, 0, _clusterLength, StringComparison.OrdinalIgnoreCase) == 0); + isClustered = instance2.Substring(_clusterLength); + continue; + } + Debug.Assert(version == null); + Debug.Assert(String.Compare(_Version, 0, instance2, 0, _versionLength, StringComparison.OrdinalIgnoreCase) == 0); + version = instance2.Substring(_versionLength); + } + + string query = "ServerName='" + serverName + "'"; + + if (!ADP.IsEmpty(instanceName)) + { // SQL BU DT 20006584: only append instanceName if present. + query += " AND InstanceName='" + instanceName + "'"; + } + + // SNI returns dupes - do not add them. SQL BU DT 290323 + if (dataTable.Select(query).Length == 0) + { + dataRow = dataTable.NewRow(); + dataRow[0] = serverName; + dataRow[1] = instanceName; + dataRow[2] = isClustered; + dataRow[3] = version; + dataTable.Rows.Add(dataRow); + } + serverName = null; + instanceName = null; + isClustered = null; + version = null; + } + foreach (DataColumn column in dataTable.Columns) + { + column.ReadOnly = true; + } + return dataTable; + } + + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj index 97336390d6..13d14c63dd 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj @@ -46,6 +46,7 @@ + @@ -68,6 +69,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlDataSourceEnumeratorTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlDataSourceEnumeratorTest.cs new file mode 100644 index 0000000000..cc5b936fda --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlDataSourceEnumeratorTest.cs @@ -0,0 +1,21 @@ +using System.Data; +using System.ServiceProcess; +using Microsoft.Data.Sql; +using Xunit; + +namespace Microsoft.Data.SqlClient.Tests +{ + internal class SqlDataSourceEnumeratorTest + { + [PlatformSpecific(TestPlatforms.Windows)] + public void SqlDataSourceEnumerator_VerfifyDataTableSize() + { + ServiceController sc = new ServiceController("SQLBrowser"); + Assert.Equal(ServiceControllerStatus.Running, sc.Status); + + SqlDataSourceEnumerator instance = SqlDataSourceEnumerator.Instance; + DataTable table = instance.GetDataSources(); + Assert.NotEmpty(table.Rows); + } + } +} From 797bf3cc3903ac65a70e00822fc4d5d917ffcb16 Mon Sep 17 00:00:00 2001 From: Kaur-Parminder Date: Wed, 8 Dec 2021 14:00:39 -0800 Subject: [PATCH 2/3] Netfx SNINativeWrapper changes Netfx SNINativeWrapper changes --- .../Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs index b700e4b108..276e8cdc29 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs @@ -134,5 +134,14 @@ internal static extern unsafe uint SNISecGenClientContextWrapper( [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr SNIClientCertificateFallbackWrapper(IntPtr pCallbackContext); + + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumOpenWrapper")] + internal static extern IntPtr SNIServerEnumOpen(); + + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumCloseWrapper")] + internal static extern void SNIServerEnumClose([In] IntPtr packet); + + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumReadWrapper")] + internal static extern int SNIServerEnumRead([In] IntPtr packet, [In, Out] char[] readBuffer, int bufferLength, out bool more); } } From 743a6075c2fea8fc7672fb9766e35192aa33fe30 Mon Sep 17 00:00:00 2001 From: Kaur-Parminder Date: Sun, 12 Dec 2021 16:54:03 -0800 Subject: [PATCH 3/3] addressing reviewer's comment addressing reviewer's comment --- .../src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs | 1 - .../src/Microsoft/Data/Sql/SqlDataSourceEnumerator.cs | 9 --------- 2 files changed, 10 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs index b8fe3f3195..1365ecc86e 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs @@ -782,7 +782,6 @@ internal static void SNIServerEnumClose([In] IntPtr packet) { SNINativeManagedWrapperX86.SNIServerEnumClose(packet); } - } internal static int SNIServerEnumRead([In] IntPtr packet, [In, Out] char[] readbuffer, int bufferLength, ref bool more) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.cs index 0a25fe45b7..a5105ed621 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.cs @@ -1,13 +1,5 @@ -//------------------------------------------------------------------------------ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// Microsoft -// Microsoft -//------------------------------------------------------------------------------ using Microsoft.Data.Common; using Microsoft.Data.SqlClient; - using System; using System.Data; using System.Data.Common; @@ -193,6 +185,5 @@ static private System.Data.DataTable ParseServerEnumString(string serverInstance } return dataTable; } - } }