e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
360 lines
16 KiB
C#
360 lines
16 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="LocalDB.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
// <owner current="true" primary="true">antonam</owner>
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
namespace System.Data
|
|
{
|
|
using System.Configuration;
|
|
using System.Threading;
|
|
using System.Runtime.InteropServices;
|
|
using System.Data.Common;
|
|
using System.Globalization;
|
|
using System.Data.SqlClient;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text;
|
|
using System.Diagnostics;
|
|
using System.Security;
|
|
using System.Security.Permissions;
|
|
|
|
internal static class LocalDBAPI
|
|
{
|
|
const string const_localDbPrefix = @"(localdb)\";
|
|
const string const_partialTrustFlagKey = "ALLOW_LOCALDB_IN_PARTIAL_TRUST";
|
|
|
|
static PermissionSet _fullTrust = null;
|
|
static bool _partialTrustFlagChecked = false;
|
|
static bool _partialTrustAllowed = false;
|
|
|
|
|
|
// check if name is in format (localdb)\<InstanceName - not empty> and return instance name if it is
|
|
internal static string GetLocalDbInstanceNameFromServerName(string serverName)
|
|
{
|
|
if (serverName == null)
|
|
return null;
|
|
serverName = serverName.TrimStart(); // it can start with spaces if specified in quotes
|
|
if (!serverName.StartsWith(const_localDbPrefix, StringComparison.OrdinalIgnoreCase))
|
|
return null;
|
|
string instanceName = serverName.Substring(const_localDbPrefix.Length).Trim();
|
|
if (instanceName.Length == 0)
|
|
return null;
|
|
else
|
|
return instanceName;
|
|
}
|
|
|
|
#if !MONO
|
|
internal static void ReleaseDLLHandles()
|
|
{
|
|
s_userInstanceDLLHandle = IntPtr.Zero;
|
|
s_localDBFormatMessage = null;
|
|
s_localDBCreateInstance = null;
|
|
}
|
|
|
|
|
|
|
|
//This is copy of handle that SNI maintains, so we are responsible for freeing it - therefore there we are not using SafeHandle
|
|
static IntPtr s_userInstanceDLLHandle = IntPtr.Zero;
|
|
|
|
static object s_dllLock = new object();
|
|
|
|
static IntPtr UserInstanceDLLHandle
|
|
{
|
|
get
|
|
{
|
|
if (s_userInstanceDLLHandle==IntPtr.Zero)
|
|
{
|
|
bool lockTaken = false;
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
try
|
|
{
|
|
Monitor.Enter(s_dllLock, ref lockTaken);
|
|
if (s_userInstanceDLLHandle == IntPtr.Zero)
|
|
{
|
|
SNINativeMethodWrapper.SNIQueryInfo(SNINativeMethodWrapper.QTypes.SNI_QUERY_LOCALDB_HMODULE, ref s_userInstanceDLLHandle);
|
|
if (s_userInstanceDLLHandle != IntPtr.Zero)
|
|
{
|
|
Bid.Trace("<sc.LocalDBAPI.UserInstanceDLLHandle> LocalDB - handle obtained");
|
|
}
|
|
else
|
|
{
|
|
SNINativeMethodWrapper.SNI_Error sniError = new SNINativeMethodWrapper.SNI_Error();
|
|
SNINativeMethodWrapper.SNIGetLastError(sniError);
|
|
throw CreateLocalDBException(errorMessage: Res.GetString("LocalDB_FailedGetDLLHandle"), sniError: (int)sniError.sniError);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (lockTaken)
|
|
Monitor.Exit(s_dllLock);
|
|
}
|
|
}
|
|
return s_userInstanceDLLHandle;
|
|
}
|
|
}
|
|
|
|
[SuppressUnmanagedCodeSecurity]
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate int LocalDBCreateInstanceDelegate([MarshalAs(UnmanagedType.LPWStr)] string version, [MarshalAs(UnmanagedType.LPWStr)] string instance, UInt32 flags);
|
|
|
|
static LocalDBCreateInstanceDelegate s_localDBCreateInstance = null;
|
|
|
|
static LocalDBCreateInstanceDelegate LocalDBCreateInstance
|
|
{
|
|
get
|
|
{
|
|
if (s_localDBCreateInstance==null)
|
|
{
|
|
bool lockTaken = false;
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
try
|
|
{
|
|
Monitor.Enter(s_dllLock, ref lockTaken);
|
|
if (s_localDBCreateInstance == null)
|
|
{
|
|
IntPtr functionAddr = SafeNativeMethods.GetProcAddress(UserInstanceDLLHandle, "LocalDBCreateInstance");
|
|
|
|
if (functionAddr == IntPtr.Zero)
|
|
{
|
|
int hResult=Marshal.GetLastWin32Error();
|
|
Bid.Trace("<sc.LocalDBAPI.LocalDBCreateInstance> GetProcAddress for LocalDBCreateInstance error 0x{%X}",hResult);
|
|
throw CreateLocalDBException(errorMessage: Res.GetString("LocalDB_MethodNotFound"));
|
|
}
|
|
s_localDBCreateInstance = (LocalDBCreateInstanceDelegate)Marshal.GetDelegateForFunctionPointer(functionAddr, typeof(LocalDBCreateInstanceDelegate));
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (lockTaken)
|
|
Monitor.Exit(s_dllLock);
|
|
}
|
|
}
|
|
return s_localDBCreateInstance;
|
|
}
|
|
}
|
|
|
|
|
|
[SuppressUnmanagedCodeSecurity]
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl,CharSet=CharSet.Unicode)]
|
|
private delegate int LocalDBFormatMessageDelegate(int hrLocalDB, UInt32 dwFlags, UInt32 dwLanguageId, StringBuilder buffer, ref UInt32 buflen);
|
|
|
|
static LocalDBFormatMessageDelegate s_localDBFormatMessage = null;
|
|
|
|
static LocalDBFormatMessageDelegate LocalDBFormatMessage
|
|
{
|
|
get
|
|
{
|
|
if (s_localDBFormatMessage==null)
|
|
{
|
|
bool lockTaken = false;
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
try
|
|
{
|
|
Monitor.Enter(s_dllLock, ref lockTaken);
|
|
if (s_localDBFormatMessage == null)
|
|
{
|
|
IntPtr functionAddr = SafeNativeMethods.GetProcAddress(UserInstanceDLLHandle, "LocalDBFormatMessage");
|
|
|
|
if (functionAddr == IntPtr.Zero)
|
|
{
|
|
// SNI checks for LocalDBFormatMessage during DLL loading, so it is practically impossibe to get this error.
|
|
int hResult=Marshal.GetLastWin32Error();
|
|
Bid.Trace("<sc.LocalDBAPI.LocalDBFormatMessage> GetProcAddress for LocalDBFormatMessage error 0x{%X}", hResult);
|
|
throw CreateLocalDBException(errorMessage: Res.GetString("LocalDB_MethodNotFound"));
|
|
}
|
|
s_localDBFormatMessage = (LocalDBFormatMessageDelegate)Marshal.GetDelegateForFunctionPointer(functionAddr, typeof(LocalDBFormatMessageDelegate));
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (lockTaken)
|
|
Monitor.Exit(s_dllLock);
|
|
}
|
|
}
|
|
return s_localDBFormatMessage;
|
|
}
|
|
}
|
|
|
|
const UInt32 const_LOCALDB_TRUNCATE_ERR_MESSAGE = 1;// flag for LocalDBFormatMessage that indicates that message can be truncated if it does not fit in the buffer
|
|
const int const_ErrorMessageBufferSize = 1024; // Buffer size for Local DB error message, according to Serverless team, 1K will be enough for all messages
|
|
|
|
|
|
internal static string GetLocalDBMessage(int hrCode)
|
|
{
|
|
Debug.Assert(hrCode < 0, "HRCode does not indicate error");
|
|
try
|
|
{
|
|
StringBuilder buffer = new StringBuilder((int)const_ErrorMessageBufferSize);
|
|
UInt32 len = (UInt32)buffer.Capacity;
|
|
|
|
|
|
// First try for current culture
|
|
int hResult=LocalDBFormatMessage(hrLocalDB: hrCode, dwFlags: const_LOCALDB_TRUNCATE_ERR_MESSAGE, dwLanguageId: (UInt32)CultureInfo.CurrentCulture.LCID,
|
|
buffer: buffer, buflen: ref len);
|
|
if (hResult>=0)
|
|
return buffer.ToString();
|
|
else
|
|
{
|
|
// Message is not available for current culture, try default
|
|
buffer = new StringBuilder((int)const_ErrorMessageBufferSize);
|
|
len = (UInt32) buffer.Capacity;
|
|
hResult=LocalDBFormatMessage(hrLocalDB: hrCode, dwFlags: const_LOCALDB_TRUNCATE_ERR_MESSAGE, dwLanguageId: 0 /* thread locale with fallback to English */,
|
|
buffer: buffer, buflen: ref len);
|
|
if (hResult >= 0)
|
|
return buffer.ToString();
|
|
else
|
|
return string.Format(CultureInfo.CurrentCulture, "{0} (0x{1:X}).", Res.GetString("LocalDB_UnobtainableMessage"), hResult);
|
|
}
|
|
}
|
|
catch (SqlException exc)
|
|
{
|
|
return string.Format(CultureInfo.CurrentCulture, "{0} ({1}).", Res.GetString("LocalDB_UnobtainableMessage"), exc.Message);
|
|
}
|
|
}
|
|
|
|
|
|
static SqlException CreateLocalDBException(string errorMessage, string instance = null, int localDbError = 0, int sniError = 0)
|
|
{
|
|
Debug.Assert((localDbError == 0) || (sniError == 0), "LocalDB error and SNI error cannot be specified simultaneously");
|
|
Debug.Assert(!string.IsNullOrEmpty(errorMessage), "Error message should not be null or empty");
|
|
SqlErrorCollection collection = new SqlErrorCollection();
|
|
|
|
int errorCode = (localDbError == 0) ? sniError : localDbError;
|
|
|
|
if (sniError!=0)
|
|
{
|
|
string sniErrorMessage = SQL.GetSNIErrorMessage(sniError);
|
|
errorMessage = String.Format((IFormatProvider)null, "{0} (error: {1} - {2})",
|
|
errorMessage, sniError, sniErrorMessage);
|
|
}
|
|
|
|
collection.Add(new SqlError(errorCode, 0, TdsEnums.FATAL_ERROR_CLASS, instance, errorMessage, null, 0));
|
|
|
|
if (localDbError != 0)
|
|
collection.Add(new SqlError(errorCode, 0, TdsEnums.FATAL_ERROR_CLASS, instance, GetLocalDBMessage(localDbError), null, 0));
|
|
|
|
SqlException exc = SqlException.CreateException(collection, null);
|
|
|
|
exc._doNotReconnect = true;
|
|
|
|
return exc;
|
|
}
|
|
|
|
private class InstanceInfo
|
|
{
|
|
internal InstanceInfo(string version)
|
|
{
|
|
this.version = version;
|
|
this.created = false;
|
|
}
|
|
|
|
internal readonly string version;
|
|
internal bool created;
|
|
}
|
|
|
|
static object s_configLock=new object();
|
|
static Dictionary<string, InstanceInfo> s_configurableInstances = null;
|
|
|
|
internal static void DemandLocalDBPermissions()
|
|
{
|
|
if (!_partialTrustAllowed)
|
|
{
|
|
if (!_partialTrustFlagChecked)
|
|
{
|
|
object partialTrustFlagValue = AppDomain.CurrentDomain.GetData(const_partialTrustFlagKey);
|
|
if (partialTrustFlagValue != null && partialTrustFlagValue is bool)
|
|
{
|
|
_partialTrustAllowed = (bool)partialTrustFlagValue;
|
|
}
|
|
_partialTrustFlagChecked = true;
|
|
if (_partialTrustAllowed)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
if (_fullTrust == null)
|
|
{
|
|
_fullTrust = new NamedPermissionSet("FullTrust");
|
|
}
|
|
_fullTrust.Demand();
|
|
}
|
|
}
|
|
|
|
internal static void AssertLocalDBPermissions()
|
|
{
|
|
_partialTrustAllowed = true;
|
|
}
|
|
|
|
|
|
internal static void CreateLocalDBInstance(string instance)
|
|
{
|
|
DemandLocalDBPermissions();
|
|
if (s_configurableInstances == null)
|
|
{
|
|
// load list of instances from configuration, mark them as not created
|
|
bool lockTaken = false;
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
try
|
|
{
|
|
Monitor.Enter(s_configLock, ref lockTaken);
|
|
if (s_configurableInstances == null)
|
|
{
|
|
Dictionary<string, InstanceInfo> tempConfigurableInstances = new Dictionary<string, InstanceInfo>(StringComparer.OrdinalIgnoreCase);
|
|
#if !NO_CONFIGURATION
|
|
object section = PrivilegedConfigurationManager.GetSection("system.data.localdb");
|
|
if (section != null) // if no section just skip creation
|
|
{
|
|
// validate section type
|
|
LocalDBConfigurationSection configSection = section as LocalDBConfigurationSection;
|
|
if (configSection == null)
|
|
throw CreateLocalDBException(errorMessage: Res.GetString("LocalDB_BadConfigSectionType"));
|
|
foreach (LocalDBInstanceElement confElement in configSection.LocalDbInstances)
|
|
{
|
|
Debug.Assert(confElement.Name != null && confElement.Version != null, "Both name and version should not be null");
|
|
tempConfigurableInstances.Add(confElement.Name.Trim(), new InstanceInfo(confElement.Version.Trim()));
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
Bid.Trace( "<sc.LocalDBAPI.CreateLocalDBInstance> No system.data.localdb section found in configuration");
|
|
s_configurableInstances = tempConfigurableInstances;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (lockTaken)
|
|
Monitor.Exit(s_configLock);
|
|
}
|
|
}
|
|
|
|
InstanceInfo instanceInfo = null;
|
|
|
|
if (!s_configurableInstances.TryGetValue(instance,out instanceInfo))
|
|
return; // instance name was not in the config
|
|
|
|
if (instanceInfo.created)
|
|
return; // instance has already been created
|
|
|
|
Debug.Assert(!instance.Contains("\0"), "Instance name should contain embedded nulls");
|
|
|
|
if (instanceInfo.version.Contains("\0"))
|
|
throw CreateLocalDBException(errorMessage: Res.GetString("LocalDB_InvalidVersion"), instance: instance);
|
|
|
|
// LocalDBCreateInstance is thread- and cross-process safe method, it is OK to call from two threads simultaneously
|
|
int hr = LocalDBCreateInstance(instanceInfo.version, instance, flags: 0);
|
|
Bid.Trace("<sc.LocalDBAPI.CreateLocalDBInstance> Starting creation of instance %ls version %ls", instance, instanceInfo.version);
|
|
if (hr < 0)
|
|
throw CreateLocalDBException(errorMessage: Res.GetString("LocalDB_CreateFailed"), instance: instance, localDbError: hr);
|
|
Bid.Trace("<sc.LocalDBAPI.CreateLocalDBInstance> Finished creation of instance %ls", instance);
|
|
instanceInfo.created=true; // mark instance as created
|
|
|
|
} // CreateLocalDbInstance
|
|
#endif
|
|
}
|
|
|
|
}
|