Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
using System.IO;
using System.Runtime.CompilerServices;
using System.Security;
using Microsoft.Data.SqlClient;
Comment thread
paulmedynski marked this conversation as resolved.
Outdated
using System.Security.Authentication;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Transactions;
using Microsoft.Data.Common.ConnectionString;
using Microsoft.Data.SqlClient;
using Microsoft.Data.SqlClient.Connection;
using Microsoft.SqlServer.Server;
using Microsoft.Win32;
Expand Down Expand Up @@ -65,22 +65,6 @@ internal static partial class ADP
/// </summary>
internal const int MaxBufferAccessTokenExpiry = 600;

/// <summary>
/// This member returns true if the current OS platform is Windows.
/// </summary>
/// <remarks>
/// This is a const on .NET Framework, and a property on .NET Core, because of differing API availability and JIT requirements.
/// .NET Framework will perform basic dead branch elimination when a const value is encountered, while .NET Core can trim Windows-specific
/// code when published to non-Windows platforms.
/// .NET Core's trimming is very limited though, so this must be used inline within methods to throw PlatformNotSupportedException,
/// rather than in a throw helper.
/// </remarks>
#if NETFRAMEWORK
public const bool IsWindows = true;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't strictly true - you can run .NET Framework on non-Windows.

We're conflating .NET Framework and Windows elsewhere, and we will deal with that later.

#else
public static bool IsWindows => OperatingSystem.IsWindows();
#endif

#region UDT

#if NETFRAMEWORK
Expand Down Expand Up @@ -159,10 +143,10 @@ internal static Timer UnsafeCreateTimer(
state,
TimeSpan.FromMilliseconds(dueTimeMilliseconds),
TimeSpan.FromMilliseconds(periodMilliseconds));

internal static Timer UnsafeCreateTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
{
// Don't capture the current ExecutionContext and its AsyncLocals onto
// Don't capture the current ExecutionContext and its AsyncLocals onto
// a global timer causing them to live forever
bool restoreFlow = false;
try
Expand All @@ -184,7 +168,7 @@ internal static Timer UnsafeCreateTimer(TimerCallback callback, object state, Ti
}
}
}


#region COM+ exceptions
internal static ArgumentException Argument(string error)
Expand Down Expand Up @@ -451,7 +435,7 @@ internal static ArgumentOutOfRangeException InvalidCommandBehavior(CommandBehavi
internal static object LocalMachineRegistryValue(string subkey, string queryvalue)
{
#if NET
if (!IsWindows)
if (OsConstants.IsUnix)
Comment thread
paulmedynski marked this conversation as resolved.
Outdated
{
// No registry in non-Windows environments
return null;
Expand Down Expand Up @@ -639,7 +623,7 @@ internal static string BuildMultiPartName(string[] strings)
{
StringBuilder bld = new();
// Assume we want to build a full multi-part name with all parts except trimming separators for
// leading empty names (null or empty strings, but not whitespace). Separators in the middle
// leading empty names (null or empty strings, but not whitespace). Separators in the middle
// should be added, even if the name part is null/empty, to maintain proper location of the parts.
for (int i = 0; i < strings.Length; i++)
{
Expand Down Expand Up @@ -839,14 +823,14 @@ internal static Version GetAssemblyVersion()
/// Represents a collection of Azure SQL Server endpoint URLs for various regions and environments.
/// </summary>
/// <remarks>This array includes endpoint URLs for Azure SQL in global, Germany, US Government,
/// China, and Fabric environments. These endpoints are used to identify and interact with Azure SQL services
/// China, and Fabric environments. These endpoints are used to identify and interact with Azure SQL services
/// in their respective regions or environments.</remarks>
internal static readonly List<string> s_azureSqlServerEndpoints = new() { AZURE_SQL,
AZURE_SQL_GERMANY,
AZURE_SQL_USGOV,
AZURE_SQL_CHINA,
AZURE_SQL_FABRIC };

/// <summary>
/// Contains endpoint strings for Azure SQL Server on-demand services.
/// Each entry is a combination of the ONDEMAND_PREFIX and a specific Azure SQL endpoint string.
Expand All @@ -872,9 +856,9 @@ internal static Version GetAssemblyVersion()
internal static bool IsAzureSynapseOnDemandEndpoint(string dataSource)
{
return IsEndpoint(dataSource, s_azureSynapseOnDemandEndpoints)
|| dataSource.IndexOf(AZURE_SYNAPSE, StringComparison.OrdinalIgnoreCase) >= 0;
|| dataSource.IndexOf(AZURE_SYNAPSE, StringComparison.OrdinalIgnoreCase) >= 0;
}

internal static bool IsAzureSqlServerEndpoint(string dataSource)
{
return IsEndpoint(dataSource, s_azureSqlServerEndpoints);
Expand Down Expand Up @@ -1088,7 +1072,7 @@ internal enum InternalErrorCode

SqlDependencyObtainProcessDispatcherFailureObjectHandle = 50,
SqlDependencyProcessDispatcherFailureCreateInstance = 51,

SqlDependencyCommandHashIsNotAssociatedWithNotification = 53,

UnknownTransactionFailure = 60,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ public static bool UseManagedNetworking
return s_useManagedNetworking == SwitchValue.True;
}

if (!OperatingSystem.IsWindows())
if (OsConstants.IsUnix)
{
s_useManagedNetworking = SwitchValue.True;
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// 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.Runtime.InteropServices;

namespace Microsoft.Data.SqlClient;

/// <summary>
/// Provides platform detection flags for OS-specific code paths.
/// </summary>
/// <remarks>
/// These constants are computed at runtime and cached as static readonly fields. This design
/// allows the JIT compiler to elide branches in hot paths based on whether the OS flags are known
/// constants at JIT compilation time.
/// </remarks>
internal static class OsConstants
{
/// <summary>
/// Gets a value indicating whether the runtime is executing on Windows.
/// </summary>
internal static readonly bool IsWindows;

/// <summary>
/// Gets a value indicating whether the runtime is executing on Linux.
/// </summary>
internal static readonly bool IsLinux;

/// <summary>
/// Gets a value indicating whether the runtime is executing on macOS.
/// </summary>
internal static readonly bool IsMacOS;

#if NET
Comment thread
paulmedynski marked this conversation as resolved.
Outdated
/// <summary>
/// Gets a value indicating whether the runtime is executing on FreeBSD.
/// </summary>
/// <remarks>
/// FreeBSD support is only available in .NET 5+ and later. This field will be
/// <c>false</c> on .NET Framework or if the runtime does not support FreeBSD detection.
/// </remarks>
internal static readonly bool IsFreeBSD;
#endif

/// <summary>
/// Gets a value indicating whether the runtime is executing on a Unix-like operating system.
/// </summary>
/// <remarks>
/// This is <c>true</c> for Linux, macOS, FreeBSD, and other Unix-like platforms.
/// It is <c>false</c> only on Windows.
/// </remarks>
internal static readonly bool IsUnix;
Comment thread
paulmedynski marked this conversation as resolved.
Outdated

/// <summary>
/// Initializes platform detection flags by querying <see cref="RuntimeInformation"/>.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This impacts IL trimming, which runs at publish time. The IL trimmer needs to be able to statically see that IsWindows will always be false on Linux, otherwise it'll leave the Windows-specific code (including the native SNI, with all its dependencies on the native DLL...) in the Linux app.

I've linked against the CI artifacts and confirmed that the IL trimmer can't analyze the static constructor, so I think this rules out everything besides exposing properties which returns the value of RuntimeInformation.IsOSPlatform (as ADP.IsWindows currently does - but expanded for each OS.)

Incidentally, exposing it as a field or a property also blocks .NET Framework from removing dead code paths (which in turn can consume the inlining budget), which is why IsWindows is currently a const. I've not got a strong preference there though, perhaps the performance gains are small enough that correctness is more important here.

/// </summary>
/// <remarks>
/// We use a static constructor instead of a module initializer ([ModuleInitializer]) to avoid
/// the CA2255 security concern. Module initializers can be problematic because: 1. They run in
/// an unpredictable order relative to other initialization code. 2. They run before the app
/// initialization sequence, potentially before security policies are set. 3. They can
/// complicate debugging and profiling.
///
/// Using a static constructor ensures initialization happens in a well-defined, type-safe
/// manner that is compatible with the CLR's type loading guarantees.
///
/// The trade-off is that the OS flags won't be initialized until the OsConstants type is first
/// accessed, which may cause a slight delay in a hot path, but only once.
/// </remarks>
static OsConstants()
{
IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
IsMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
#if NET
IsFreeBSD = RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD);
IsUnix = IsLinux || IsMacOS || IsFreeBSD;
#else
IsUnix = IsLinux || IsMacOS;
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public class SqlColumnEncryptionCertificateStoreProvider : SqlColumnEncryptionKe
/// Gets a string array containing valid certificate locations.
/// </summary>
private static string[] ValidCertificateLocations =>
Environment.OSVersion.Platform == PlatformID.Win32NT
OsConstants.IsWindows
? [CertLocationLocalMachine, CertLocationCurrentUser]
: [CertLocationCurrentUser];

Expand Down Expand Up @@ -158,14 +158,13 @@ private static RSA GetCertificatePrivateKeyByPath(string keyPath, bool isSystemO
}

// Extract the store location where the cert is stored
if (storeLocationSpan.IsEmpty
&& Environment.OSVersion.Platform == PlatformID.Win32NT)
if (storeLocationSpan.IsEmpty && OsConstants.IsWindows)
{
// Default to Local Machine on Windows. Non-Windows platforms only support CurrentUser
storeLocation = StoreLocation.LocalMachine;
}
else if (storeLocationSpan.Equals(CertLocationLocalMachine.AsSpan(), StringComparison.OrdinalIgnoreCase)
&& Environment.OSVersion.Platform == PlatformID.Win32NT)
&& OsConstants.IsWindows)
{
storeLocation = StoreLocation.LocalMachine;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ private static void GetCngProviderAndKeyId(string keyPath, bool isSystemOp, out
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCngProvider.xml' path='docs/members[@name="SqlColumnEncryptionCngProvider"]/DecryptColumnEncryptionKey/*' />
public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey)
{
if (!ADP.IsWindows)
if (OsConstants.IsUnix)
Comment thread
paulmedynski marked this conversation as resolved.
Outdated
{
throw new PlatformNotSupportedException();
}
Expand Down Expand Up @@ -180,7 +180,7 @@ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string?
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCngProvider.xml' path='docs/members[@name="SqlColumnEncryptionCngProvider"]/EncryptColumnEncryptionKey/*' />
public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey)
{
if (!ADP.IsWindows)
if (OsConstants.IsUnix)
{
throw new PlatformNotSupportedException();
}
Expand Down Expand Up @@ -211,15 +211,15 @@ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string?
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCngProvider.xml' path='docs/members[@name="SqlColumnEncryptionCngProvider"]/SignColumnMasterKeyMetadata/*' />
public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations)
{
throw ADP.IsWindows
throw OsConstants.IsWindows
? new NotSupportedException()
: new PlatformNotSupportedException();
}

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCngProvider.xml' path='docs/members[@name="SqlColumnEncryptionCngProvider"]/VerifyColumnMasterKeyMetadata/*' />
public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature)
{
throw ADP.IsWindows
throw OsConstants.IsWindows
? new NotSupportedException()
: new PlatformNotSupportedException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ private static int GetProviderType(string providerName, string keyPath, bool isS
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCspProvider.xml' path='docs/members[@name="SqlColumnEncryptionCspProvider"]/DecryptColumnEncryptionKey/*' />
public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? encryptedColumnEncryptionKey)
{
if (!ADP.IsWindows)
if (OsConstants.IsUnix)
Comment thread
paulmedynski marked this conversation as resolved.
Outdated
{
throw new PlatformNotSupportedException();
}
Expand Down Expand Up @@ -182,7 +182,7 @@ public override byte[] DecryptColumnEncryptionKey(string? masterKeyPath, string?
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCspProvider.xml' path='docs/members[@name="SqlColumnEncryptionCspProvider"]/EncryptColumnEncryptionKey/*' />
public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string? encryptionAlgorithm, byte[]? columnEncryptionKey)
{
if (!ADP.IsWindows)
if (OsConstants.IsUnix)
{
throw new PlatformNotSupportedException();
}
Expand Down Expand Up @@ -213,15 +213,15 @@ public override byte[] EncryptColumnEncryptionKey(string? masterKeyPath, string?
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCspProvider.xml' path='docs/members[@name="SqlColumnEncryptionCspProvider"]/SignColumnMasterKeyMetadata/*' />
public override byte[] SignColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations)
{
throw ADP.IsWindows
throw OsConstants.IsWindows
? new NotSupportedException()
: new PlatformNotSupportedException();
}

/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCspProvider.xml' path='docs/members[@name="SqlColumnEncryptionCspProvider"]/VerifyColumnMasterKeyMetadata/*' />
public override bool VerifyColumnMasterKeyMetadata(string? masterKeyPath, bool allowEnclaveComputations, byte[]? signature)
{
throw ADP.IsWindows
throw OsConstants.IsWindows
? new NotSupportedException()
: new PlatformNotSupportedException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1376,7 +1376,7 @@ internal static Exception LargeCertificatePathLength(int actualLength, int maxLe

internal static Exception NullCertificatePath(string[] validLocations, bool isSystemOp)
{
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
if (OsConstants.IsWindows)
{
Debug.Assert(validLocations.Length == 2);
if (isSystemOp)
Expand Down Expand Up @@ -1428,7 +1428,7 @@ internal static Exception NullCngKeyPath(bool isSystemOp)

internal static Exception InvalidCertificatePath(string actualCertificatePath, string[] validLocations, bool isSystemOp)
{
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
if (OsConstants.IsWindows)
{
Debug.Assert(validLocations.Length == 2);
if (isSystemOp)
Expand Down Expand Up @@ -1564,7 +1564,7 @@ internal static Exception InvalidCngKey(string masterKeyPath, string cngProvider

internal static Exception InvalidCertificateLocation(string certificateLocation, string certificatePath, string[] validLocations, bool isSystemOp)
{
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
if (OsConstants.IsWindows)
{
Debug.Assert(validLocations.Length == 2);
if (isSystemOp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -981,7 +981,7 @@ private void EnableSsl(uint info, SqlConnectionEncryptOption encrypt, bool integ
// Channel Bindings as part of the Windows Authentication context build (SSL handshake must complete
// before calling SNISecGenClientContext).
#if NET
if (OperatingSystem.IsWindows())
if (OsConstants.IsWindows)
#endif
{
error = _physicalStateObj.WaitForSSLHandShakeToComplete(out protocol);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

using System;
using System.Globalization;
using System.Runtime.InteropServices;
using Microsoft.Data.Common;

namespace Microsoft.Data.SqlClient
Expand Down Expand Up @@ -173,7 +172,7 @@ static internal byte[] GetNetworkPhysicalAddressForTdsLoginOnly()

byte[] nicAddress = null;

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (OsConstants.IsWindows)
{
// NIC address is stored in NetworkAddress key. However, if NetworkAddressLocal key
// has a value that is not zero, then we cannot use the NetworkAddress key and must
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,25 +120,24 @@ static UserAgent()
// specific values.
//
string osType = Unknown;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (OsConstants.IsWindows)
{
osType = Windows;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
else if (OsConstants.IsLinux)
{
osType = Linux;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
else if (OsConstants.IsMacOS)
{
osType = macOS;
}
// The FreeBSD platform doesn't exist in .NET Framework at all.
#if NET
else if (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD))
#if NET
else if (OsConstants.IsFreeBSD)
{
osType = FreeBSD;
}
#endif
#endif

// Build it!
Value = Build(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public SqlFileStream(
FileOptions options,
long allocationSize)
{
if (!ADP.IsWindows)
if (OsConstants.IsUnix)
Comment thread
paulmedynski marked this conversation as resolved.
Outdated
{
throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported);
}
Expand Down
Loading