{-# LANGUAGE CPP #-}
module Hmssql.Connection.Backend
(
Backend (..)
, tcpBackend
, socketBackend
, pipeBackend
, pipesSupported
, sharedMemoryPipe
, namedPipe
, isLocalHost
, isDefaultInstance
, PipeListener
, pipeListen
, pipeAccept
, pipeListenerClose
, networkTcp
, tcpBackendWith
, connectAddresses
, addressConnectTimeout
, pipeBackendMasked
, closeSocket
, closeSocketWith
, socketCloser
) where
import Control.Exception
import Control.Monad (void, when)
import qualified Data.ByteString as B
import Data.Char (toLower)
import Data.IORef
import Data.List.NonEmpty (NonEmpty (..))
import Data.Word (Word8)
#if defined(mingw32_HOST_OS)
import Foreign.C.Types (CInt (..), CUIntPtr (..))
#else
import Foreign.C.Types (CInt (..))
#endif
import Foreign.Ptr (Ptr)
import Network.HostName (getHostName)
import Network.Socket (Socket)
import qualified Network.Socket as Socket
import qualified Network.Socket.ByteString as Socket
import System.IO.Unsafe (unsafePerformIO)
import System.Timeout (timeout)
#if defined(mingw32_HOST_OS)
import Control.Concurrent (rtsSupportsBoundThreads)
import Foreign.Ptr (intPtrToPtr)
import Hmssql.Connection.Backend.Windows hiding (PipeListener, openPipe, pipeDisconnect, pipeListenerAccept, pipeListenerClose, pipeListenerOpen)
import qualified Hmssql.Connection.Backend.Windows as Windows
import Hmssql.Connection.Backend.WinIO (PipeIO, nativeIO)
import qualified Hmssql.Connection.Backend.WinIO as WinIO
#else
import GHC.IO.Exception (IOErrorType (..), IOException (..))
#endif
data Backend = Backend
{ Backend -> Ptr Word8 -> Int -> IO Int
backendRecv :: !(Ptr Word8 -> Int -> IO Int)
, Backend -> ByteString -> IO ()
backendSend :: !(B.ByteString -> IO ())
, Backend -> [[ByteString]] -> IO ()
backendSendMany :: !([[B.ByteString]] -> IO ())
, Backend -> IO ()
backendStopReceiving :: !(IO ())
, Backend -> IO ()
backendShutdown :: !(IO ())
, Backend -> IO ()
backendClose :: !(IO ())
, Backend -> Maybe Socket
backendSocket :: !(Maybe Socket)
}
pipesSupported :: Bool
#if defined(mingw32_HOST_OS)
pipesSupported = True
#else
pipesSupported :: Bool
pipesSupported = Bool
False
#endif
networkTcp :: Bool
#if defined(mingw32_HOST_OS)
networkTcp = nativeIO
#else
networkTcp :: Bool
networkTcp = Bool
True
#endif
tcpBackend :: String -> String -> IO Backend
tcpBackend :: [Char] -> [Char] -> IO Backend
tcpBackend [Char]
host [Char]
port = ((forall a. IO a -> IO a) -> IO Backend) -> IO Backend
forall b. ((forall a. IO a -> IO a) -> IO b) -> IO b
mask (((forall a. IO a -> IO a) -> IO Backend) -> IO Backend)
-> ((forall a. IO a -> IO a) -> IO Backend) -> IO Backend
forall a b. (a -> b) -> a -> b
$ \forall a. IO a -> IO a
restore -> (forall a. IO a -> IO a) -> [Char] -> [Char] -> IO Backend
tcpBackendWith IO a -> IO a
forall a. IO a -> IO a
restore [Char]
host [Char]
port
tcpBackendWith :: (forall a. IO a -> IO a) -> String -> String -> IO Backend
tcpBackendWith :: (forall a. IO a -> IO a) -> [Char] -> [Char] -> IO Backend
tcpBackendWith forall a. IO a -> IO a
restore [Char]
host [Char]
port = do
sock <- IO (NonEmpty AddrInfo) -> IO (NonEmpty AddrInfo)
forall a. IO a -> IO a
restore IO (NonEmpty AddrInfo)
resolve IO (NonEmpty AddrInfo)
-> (NonEmpty AddrInfo -> IO Socket) -> IO Socket
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (forall a. IO a -> IO a) -> NonEmpty AddrInfo -> IO Socket
connectAddresses IO a -> IO a
forall a. IO a -> IO a
restore
socketBackend sock `onException` closeSocket sock
where
resolve :: IO (NonEmpty AddrInfo)
resolve = do
let hints :: AddrInfo
hints = AddrInfo
Socket.defaultHints { Socket.addrSocketType = Socket.Stream }
addrs <- AddrInfo -> IO [AddrInfo]
forall {t :: * -> *}. GetAddrInfo t => AddrInfo -> IO (t AddrInfo)
lookupHost AddrInfo
hints
case addrs of
AddrInfo
addr : [AddrInfo]
rest -> NonEmpty AddrInfo -> IO (NonEmpty AddrInfo)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (AddrInfo
addr AddrInfo -> [AddrInfo] -> NonEmpty AddrInfo
forall a. a -> [a] -> NonEmpty a
:| [AddrInfo]
rest)
[] -> IOException -> IO (NonEmpty AddrInfo)
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO (IOException -> IO (NonEmpty AddrInfo))
-> IOException -> IO (NonEmpty AddrInfo)
forall a b. (a -> b) -> a -> b
$ [Char] -> IOException
userError ([Char] -> IOException) -> [Char] -> IOException
forall a b. (a -> b) -> a -> b
$ [Char]
"cannot resolve " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
host
lookupHost :: AddrInfo -> IO (t AddrInfo)
lookupHost AddrInfo
hints
#if defined(mingw32_HOST_OS)
| not networkTcp = lookupAddresses hints host port
#endif
| Bool
otherwise = Maybe AddrInfo -> Maybe [Char] -> Maybe [Char] -> IO (t AddrInfo)
forall (t :: * -> *).
GetAddrInfo t =>
Maybe AddrInfo -> Maybe [Char] -> Maybe [Char] -> IO (t AddrInfo)
Socket.getAddrInfo (AddrInfo -> Maybe AddrInfo
forall a. a -> Maybe a
Just AddrInfo
hints) ([Char] -> Maybe [Char]
forall a. a -> Maybe a
Just [Char]
host) ([Char] -> Maybe [Char]
forall a. a -> Maybe a
Just [Char]
port)
addressConnectTimeout :: Int
addressConnectTimeout :: Int
addressConnectTimeout = Int
4_000_000
connectAddresses :: (forall a. IO a -> IO a) -> NonEmpty Socket.AddrInfo -> IO Socket
connectAddresses :: (forall a. IO a -> IO a) -> NonEmpty AddrInfo -> IO Socket
connectAddresses forall a. IO a -> IO a
restore = NonEmpty AddrInfo -> IO Socket
connectFirst
where
connectFirst :: NonEmpty Socket.AddrInfo -> IO Socket
connectFirst :: NonEmpty AddrInfo -> IO Socket
connectFirst (AddrInfo
addr :| [AddrInfo]
rest) = case [AddrInfo]
rest of
[] -> AddrInfo -> IO Socket
connectTo AddrInfo
addr
AddrInfo
next : [AddrInfo]
more -> IO (Maybe Socket) -> IO (Either IOException (Maybe Socket))
forall e a. Exception e => IO a -> IO (Either e a)
try (Int -> IO Socket -> IO (Maybe Socket)
forall a. Int -> IO a -> IO (Maybe a)
timeout Int
addressConnectTimeout (AddrInfo -> IO Socket
connectTo AddrInfo
addr)) IO (Either IOException (Maybe Socket))
-> (Either IOException (Maybe Socket) -> IO Socket) -> IO Socket
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Right (Just Socket
sock) -> Socket -> IO Socket
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Socket
sock
Right Maybe Socket
Nothing -> NonEmpty AddrInfo -> IO Socket
connectFirst (AddrInfo
next AddrInfo -> [AddrInfo] -> NonEmpty AddrInfo
forall a. a -> [a] -> NonEmpty a
:| [AddrInfo]
more)
Left (IOException
_ :: IOException) -> NonEmpty AddrInfo -> IO Socket
connectFirst (AddrInfo
next AddrInfo -> [AddrInfo] -> NonEmpty AddrInfo
forall a. a -> [a] -> NonEmpty a
:| [AddrInfo]
more)
connectTo :: Socket.AddrInfo -> IO Socket
connectTo :: AddrInfo -> IO Socket
connectTo AddrInfo
addr
#if defined(mingw32_HOST_OS)
| not networkTcp, rtsSupportsBoundThreads = abandonable (connectNow addr Socket.connect) closeSocket
| not networkTcp = do
sock <- newSocket addr
connectOnWorker closeSocket sock (Socket.addrAddress addr)
Socket.setSocketOption sock Socket.NoDelay 1 `onException` closeSocket sock
pure sock
#endif
| Bool
otherwise = AddrInfo -> (Socket -> SockAddr -> IO ()) -> IO Socket
connectNow AddrInfo
addr (\Socket
sock SockAddr
to -> IO () -> IO ()
forall a. IO a -> IO a
restore (Socket -> SockAddr -> IO ()
Socket.connect Socket
sock SockAddr
to))
connectNow :: Socket.AddrInfo -> (Socket -> Socket.SockAddr -> IO ()) -> IO Socket
connectNow :: AddrInfo -> (Socket -> SockAddr -> IO ()) -> IO Socket
connectNow AddrInfo
addr Socket -> SockAddr -> IO ()
connectIt = do
sock <- AddrInfo -> IO Socket
newSocket AddrInfo
addr
(do
connectIt sock (Socket.addrAddress addr)
Socket.setSocketOption sock Socket.NoDelay 1)
`onException` closeSocket sock
pure sock
newSocket :: AddrInfo -> IO Socket
newSocket AddrInfo
addr = Family -> SocketType -> CInt -> IO Socket
Socket.socket (AddrInfo -> Family
Socket.addrFamily AddrInfo
addr) (AddrInfo -> SocketType
Socket.addrSocketType AddrInfo
addr) (AddrInfo -> CInt
Socket.addrProtocol AddrInfo
addr)
socketBackend :: Socket -> IO Backend
socketBackend :: Socket -> IO Backend
socketBackend Socket
sock
#if defined(mingw32_HOST_OS)
| not networkTcp = do
requireRuntime
fd <- Socket.unsafeFdSocket sock
hio <- newHandleIO (intPtrToPtr (fromIntegral fd)) False
(Socket.shutdown sock Socket.ShutdownBoth)
(closeSocket sock)
pure (handleBackend hio) { backendSocket = Just sock }
#endif
| Bool
otherwise = Backend -> IO Backend
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Socket -> Backend
networkSocketBackend Socket
sock)
networkSocketBackend :: Socket -> Backend
networkSocketBackend :: Socket -> Backend
networkSocketBackend Socket
sock = Backend
{ backendRecv :: Ptr Word8 -> Int -> IO Int
backendRecv = Socket -> Ptr Word8 -> Int -> IO Int
Socket.recvBuf Socket
sock
, backendSend :: ByteString -> IO ()
backendSend = Socket -> ByteString -> IO ()
Socket.sendAll Socket
sock
, backendSendMany :: [[ByteString]] -> IO ()
backendSendMany = Socket -> [ByteString] -> IO ()
Socket.sendMany Socket
sock ([ByteString] -> IO ())
-> ([[ByteString]] -> [ByteString]) -> [[ByteString]] -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [[ByteString]] -> [ByteString]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat
, backendStopReceiving :: IO ()
backendStopReceiving = () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
, backendShutdown :: IO ()
backendShutdown = Socket -> ShutdownCmd -> IO ()
Socket.shutdown Socket
sock ShutdownCmd
Socket.ShutdownBoth IO () -> (IOException -> IO ()) -> IO ()
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
`catch` \(IOException
_ :: IOException) -> () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
, backendClose :: IO ()
backendClose = Socket -> IO ()
closeSocket Socket
sock
, backendSocket :: Maybe Socket
backendSocket = Socket -> Maybe Socket
forall a. a -> Maybe a
Just Socket
sock
}
closeSocket :: Socket -> IO ()
closeSocket :: Socket -> IO ()
closeSocket Socket
sock = do
closeIt <- IORef (Socket -> IO ()) -> IO (Socket -> IO ())
forall a. IORef a -> IO a
readIORef IORef (Socket -> IO ())
socketCloser
closeSocketWith closeIt sock
closeSocketWith :: (Socket -> IO ()) -> Socket -> IO ()
closeSocketWith :: (Socket -> IO ()) -> Socket -> IO ()
closeSocketWith Socket -> IO ()
closeIt Socket
sock = IO () -> IO ()
forall a. IO a -> IO a
mask_ (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
fd <- Socket -> IO CInt
Socket.unsafeFdSocket Socket
sock
closeIt sock `catch` \(SomeException
e :: SomeException) -> do
now <- Socket -> IO CInt
Socket.unsafeFdSocket Socket
sock
when (fd /= closedMark && now == closedMark) $ void (closeDescriptor (fromIntegral fd))
case fromException e of
Just (SomeAsyncException
_ :: SomeAsyncException) -> SomeException -> IO ()
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO SomeException
e
Maybe SomeAsyncException
Nothing -> () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
where
closedMark :: CInt
closedMark = Int -> CInt
forall a b. (Integral a, Num b) => a -> b
fromIntegral (-Int
1 :: Int)
socketCloser :: IORef (Socket -> IO ())
socketCloser :: IORef (Socket -> IO ())
socketCloser = IO (IORef (Socket -> IO ())) -> IORef (Socket -> IO ())
forall a. IO a -> a
unsafePerformIO ((Socket -> IO ()) -> IO (IORef (Socket -> IO ()))
forall a. a -> IO (IORef a)
newIORef Socket -> IO ()
Socket.close)
{-# NOINLINE socketCloser #-}
#if defined(mingw32_HOST_OS)
foreign import ccall unsafe "closesocket"
closeDescriptor :: CUIntPtr -> IO CInt
#else
foreign import ccall unsafe "close"
closeDescriptor :: CInt -> IO CInt
#endif
pipeBackend :: FilePath -> IO Backend
pipeBackend :: [Char] -> IO Backend
pipeBackend [Char]
path = IO Backend -> IO Backend
forall a. IO a -> IO a
mask_ ([Char] -> IO Backend
pipeBackendMasked [Char]
path)
pipeBackendMasked :: FilePath -> IO Backend
#if defined(mingw32_HOST_OS)
pipeBackendMasked path
| nativeIO = pipeIOBackend <$> WinIO.openPipe path
| otherwise = handleBackend <$> Windows.openPipe path
#else
pipeBackendMasked :: [Char] -> IO Backend
pipeBackendMasked [Char]
path = IOException -> IO Backend
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO ([Char] -> IOException
noPipes ([Char]
"open " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
path))
#endif
sharedMemoryPipe :: String -> FilePath
sharedMemoryPipe :: [Char] -> [Char]
sharedMemoryPipe [Char]
instanceName = [Char]
"\\\\.\\pipe\\SQLLocal\\" [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ if [Char] -> Bool
defaultInstance [Char]
instanceName then [Char]
"MSSQLSERVER" else [Char]
instanceName
namedPipe :: Bool -> String -> String -> FilePath
namedPipe :: Bool -> [Char] -> [Char] -> [Char]
namedPipe Bool
local [Char]
host [Char]
instanceName =
[Char]
"\\\\" [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ (if Bool
local then [Char]
"." else [Char]
host) [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"\\pipe\\"
[Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ (if [Char] -> Bool
defaultInstance [Char]
instanceName then [Char]
"" else [Char]
"MSSQL$" [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
instanceName [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"\\")
[Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"sql\\query"
defaultInstance :: String -> Bool
defaultInstance :: [Char] -> Bool
defaultInstance = [Char] -> Bool
isDefaultInstance
isDefaultInstance :: String -> Bool
isDefaultInstance :: [Char] -> Bool
isDefaultInstance [Char]
name = [Char] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Char]
name Bool -> Bool -> Bool
|| (Char -> Char) -> [Char] -> [Char]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower [Char]
name [Char] -> [Char] -> Bool
forall a. Eq a => a -> a -> Bool
== [Char]
"mssqlserver"
isLocalHost :: String -> IO Bool
isLocalHost :: [Char] -> IO Bool
isLocalHost [Char]
host
| [Char]
name [Char] -> [[Char]] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char]
"localhost", [Char]
".", [Char]
"(local)"] = Bool -> IO Bool
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
True
| Bool
otherwise = do
own <- (Char -> Char) -> [Char] -> [Char]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower ([Char] -> [Char]) -> IO [Char] -> IO [Char]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO [Char]
getHostName
pure (not (null name) && (name == own || name == takeWhile (/= '.') own))
where
name :: [Char]
name = (Char -> Char) -> [Char] -> [Char]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower [Char]
host
#if defined(mingw32_HOST_OS)
data PipeListener
= HandleListener !Windows.PipeListener
| ManagerListener !WinIO.PipeListener
#else
data PipeListener
#endif
pipeListen :: FilePath -> IO PipeListener
#if defined(mingw32_HOST_OS)
pipeListen path
| nativeIO = ManagerListener <$> WinIO.pipeListenerOpen path
| otherwise = HandleListener <$> Windows.pipeListenerOpen path
#else
pipeListen :: [Char] -> IO PipeListener
pipeListen [Char]
path = IOException -> IO PipeListener
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO ([Char] -> IOException
noPipes ([Char]
"create " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
path))
#endif
pipeAccept :: PipeListener -> IO (Backend, IO ())
#if defined(mingw32_HOST_OS)
pipeAccept listener = mask_ $ case listener of
HandleListener l -> do
hio <- Windows.pipeListenerAccept l
pure (handleBackend hio, Windows.pipeDisconnect hio)
ManagerListener l -> do
pio <- WinIO.pipeListenerAccept l
pure (pipeIOBackend pio, WinIO.pipeDisconnect pio)
#else
pipeAccept :: PipeListener -> IO (Backend, IO ())
pipeAccept PipeListener
_ = IOException -> IO (Backend, IO ())
forall e a. (HasCallStack, Exception e) => e -> IO a
throwIO ([Char] -> IOException
noPipes [Char]
"accept")
#endif
pipeListenerClose :: PipeListener -> IO ()
#if defined(mingw32_HOST_OS)
pipeListenerClose = \case
HandleListener l -> Windows.pipeListenerClose l
ManagerListener l -> WinIO.pipeListenerClose l
#else
pipeListenerClose :: PipeListener -> IO ()
pipeListenerClose PipeListener
_ = () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
noPipes :: String -> IOException
noPipes :: [Char] -> IOException
noPipes [Char]
what = Maybe Handle
-> IOErrorType
-> [Char]
-> [Char]
-> Maybe CInt
-> Maybe [Char]
-> IOException
IOError Maybe Handle
forall a. Maybe a
Nothing IOErrorType
UnsupportedOperation ([Char]
"hmssql: " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
what)
[Char]
"Shared Memory and Named Pipes are available on Windows only" Maybe CInt
forall a. Maybe a
Nothing Maybe [Char]
forall a. Maybe a
Nothing
#endif
#if defined(mingw32_HOST_OS)
handleBackend :: HandleIO -> Backend
handleBackend hio = Backend
{ backendRecv = hioRecv hio
, backendSend = hioSend hio
, backendSendMany = hioSendMany hio
, backendStopReceiving = hioStopReceiving hio
, backendShutdown = hioShutdown hio
, backendClose = hioClose hio
, backendSocket = Nothing
}
pipeIOBackend :: PipeIO -> Backend
pipeIOBackend pio = Backend
{ backendRecv = WinIO.pioRecv pio
, backendSend = \bs -> WinIO.pioSendMany pio [[bs]]
, backendSendMany = WinIO.pioSendMany pio
, backendStopReceiving = WinIO.pioStopReceiving pio
, backendShutdown = WinIO.pioClose pio
, backendClose = WinIO.pioClose pio
, backendSocket = Nothing
}
#endif