sockets-0.6.1.0: src/Socket/Datagram/Unix/Connected.hs
{-# language BangPatterns #-}
{-# language DataKinds #-}
{-# language DeriveAnyClass #-}
{-# language DerivingStrategies #-}
{-# language DuplicateRecordFields #-}
{-# language LambdaCase #-}
{-# language MagicHash #-}
{-# language NamedFieldPuns #-}
{-# language UnboxedTuples #-}
{-# language ScopedTypeVariables #-}
{-# language TypeApplications #-}
-- | Unix-domain datagram sockets with a fixed destination.
module Socket.Datagram.Unix.Connected
( -- * Types
Socket(..)
, Family(..)
, Connectedness(..)
, UnixAddress(..)
, Message(..)
-- * Establish
, withSocket
, withPair
, connect
, open
, openPair
, close
-- * Exceptions
, SocketException(..)
, ConnectException(..)
, SD.ReceiveException(..)
, SD.SendException(..)
-- * Examples
-- $examples
) where
import Control.Exception (mask,onException)
import Data.Coerce (coerce)
import Data.Primitive (ByteArray)
import Foreign.C.Error (Errno(..),eNOENT,ePROTOTYPE)
import Foreign.C.Error (eNFILE,eMFILE,eCONNREFUSED)
import Socket (Connectedness(..),Family(..))
import Socket.Datagram (Socket(..))
import Socket (SocketException(..))
import Socket.Stream (ConnectException(..))
import Socket.Error (die)
import Socket.IPv4 (Message(..))
import Socket.Datagram.Common (close)
import Socket (Interruptibility(Uninterruptible))
import System.Posix.Types (Fd)
import qualified Foreign.C.Error.Describe as D
import qualified Linux.Socket as L
import qualified Posix.Socket as S
import qualified Socket as SCK
import qualified Socket.Datagram as SD
import qualified Socket.EventManager as EM
import qualified Socket.Pair as Pair
newtype UnixAddress = UnixAddress ByteArray
-- | Unbracketed function for opening a socket. Be careful with
-- this function. The resulting socket is a UNIX-domain datagram socket
-- on which @connect@ was called without first calling @bind@. This means
-- that it corresponds neither to an entry in the filesystem nor an entry
-- in the abstract socket namespace.
open ::
UnixAddress
-> IO (Either (ConnectException 'Unix 'Uninterruptible) (Socket 'Connected 'SCK.Unix))
open (UnixAddress remote) = do
-- TODO: This is somewhat copied from the internet-domain
-- socket code
e1 <- S.uninterruptibleSocket S.Unix
(L.applySocketFlags (L.closeOnExec <> L.nonblocking) S.datagram)
S.defaultProtocol
case e1 of
Left err -> handleSocketException err
Right fd -> do
let !mngr = EM.manager
EM.register mngr fd
let sockAddr = id
$ S.encodeSocketAddressUnix
$ S.SocketAddressUnix
$ remote
S.uninterruptibleConnect fd sockAddr >>= \case
Left err -> handleConnectException err
Right (_ :: ()) -> pure (Right (Socket fd))
-- | Unbracketed function for opening a connected socket pair. All warnings
-- that apply to 'open' apply to this function as well.
openPair :: IO (Either SocketException (Socket 'Connected 'SCK.Unix, Socket 'Connected 'SCK.Unix))
openPair = coerce
@(IO (Either SocketException (Fd,Fd)))
@(IO (Either SocketException (Socket 'Connected 'SCK.Unix, Socket 'Connected 'SCK.Unix)))
(Pair.open S.sequencedPacket)
-- | Run a callback that requires a pair of connected
-- datagram sockets. The sockets will be closed when the
-- callback completes.
withPair ::
(Socket 'Connected 'SCK.Unix -> Socket 'Connected 'SCK.Unix -> IO a)
-- ^ Callback providing the connected datagram sockets
-> IO (Either SocketException a)
withPair f = mask $ \restore -> openPair >>= \case
Left err -> pure (Left err)
Right (Socket fdA, Socket fdB) -> do
a <- onException
(restore (f (Socket fdA) (Socket fdB)))
(S.uninterruptibleErrorlessClose fdA *> S.uninterruptibleErrorlessClose fdB)
S.uninterruptibleClose fdA >>= \case
Left err -> die ("Socket.Datagram.Unix.Connected.close: " ++ describeErrorCode err)
Right _ -> S.uninterruptibleClose fdB >>= \case
Left err -> die ("Socket.Datagram.Unix.Connected.close: " ++ describeErrorCode err)
Right _ -> pure (Right a)
withSocket ::
UnixAddress
-- ^ Peer address (to connect to)
-> (Socket 'Connected 'SCK.Unix -> IO a)
-- ^ Callback providing the socket and the chosen port
-> IO (Either (ConnectException 'Unix 'Uninterruptible) a)
withSocket !peer f = mask $ \restore -> open peer >>= \case
Left err -> pure (Left err)
Right (Socket fd) -> do
a <- onException (restore (f (Socket fd))) (S.uninterruptibleErrorlessClose fd)
S.uninterruptibleClose fd >>= \case
Left err -> die ("Socket.Datagram.Unix.Connected.close: " ++ describeErrorCode err)
Right _ -> pure (Right a)
-- | Set the endpoint to connect to.
connect ::
UnixAddress
-- ^ Peer address
-> Socket 'Connected 'SCK.Unix
-- ^ Unix-domain datagram socket
-> IO (Either (ConnectException 'Unix 'Uninterruptible) ())
connect (UnixAddress remote) (Socket fd) = do
let sockAddr = id
$ S.encodeSocketAddressUnix
$ S.SocketAddressUnix
$ remote
S.uninterruptibleConnect fd sockAddr >>= \case
Left err -> handleConnectException err
Right (_ :: ()) -> pure (Right ())
describeErrorCode :: Errno -> String
describeErrorCode err@(Errno e) = "error code " ++ D.string err ++ " (" ++ show e ++ ")"
handleSocketException :: Errno -> IO (Either (ConnectException 'Unix i) a)
handleSocketException e
| e == eMFILE = pure (Left ConnectFileDescriptorLimit)
| e == eNFILE = pure (Left ConnectFileDescriptorLimit)
| otherwise = die
("Socket.Datagram.Unix.Connected.socket: " ++ describeErrorCode e)
handleConnectException :: Errno -> IO (Either (ConnectException 'Unix i) a)
handleConnectException e
| e == eNOENT = pure (Left ConnectNoEntry)
| e == ePROTOTYPE = pure (Left ConnectProtocolType)
| e == eCONNREFUSED = pure (Left ConnectRefused)
| otherwise = die
("Socket.Datagram.Unix.Connected.connect: " ++ describeErrorCode e)