packages feed

spacecookie-0.2.1.0: server/Systemd.hs

{-# LANGUAGE BlockArguments #-}
module Systemd
  ( systemdSocket
  , notifyReady
  , notifyStopping
  , systemdStoreOrClose
  ) where

import Control.Concurrent.MVar (newMVar, takeMVar, mkWeakMVar)
import Control.Exception.Base
import Control.Monad (when, void)
import Foreign.C.Types (CInt (..))
import GHC.Conc (closeFdWith)
import Network.Gopher (setupGopherSocket, GopherConfig (..))
import System.Exit
import System.Posix.Types (Fd (..))
import System.Socket hiding (Error (..))
import System.Socket.Family.Inet6
import System.Socket.Type.Stream
import System.Socket.Protocol.TCP
import System.Socket.Unsafe (Socket (..))
import System.Systemd.Daemon (notifyReady, notifyStopping)
import System.Systemd.Daemon.Fd (storeFd, getActivatedSockets)

foreign import ccall unsafe "close"
  c_close :: CInt -> IO CInt

-- TODO Check Socket type, ...
data SystemdException = IncorrectNum | InvalidFd
  deriving (Eq, Ord)

instance Show SystemdException where
  show IncorrectNum = "SystemdException: Only exactly one Socket is supported"
  show InvalidFd    = "SystemdException: Invalid File Descriptor received"
instance Exception SystemdException

systemdSocket :: GopherConfig -> IO (Socket Inet6 Stream TCP)
systemdSocket cfg = getActivatedSockets >>= \sockets ->
  case sockets of
    Nothing -> setupGopherSocket cfg
    Just [fd] -> toSocket fd
    Just _ -> throwIO IncorrectNum
  where toSocket :: Fd -> IO (Socket Inet6 Stream TCP)
        toSocket fd = do
          when (fd < 0) $ throwIO InvalidFd
          mfd <- newMVar (fromIntegral fd)
          let s = Socket mfd
          _ <- mkWeakMVar mfd (close s)
          pure s

systemdStoreOrClose :: Socket Inet6 Stream TCP -> IO ()
systemdStoreOrClose s = do
  fd <- toFd s
  res <- storeFd fd
  case res of
    Just () -> return ()
    Nothing -> closeFdWith (void . c_close . fromIntegral) fd
  where toFd :: Socket Inet6 Stream TCP -> IO Fd
        toFd (Socket mvar) = fmap (Fd . fromIntegral) (takeMVar mvar)