module System.Systemd.Daemon (
notify
, notifyWithFD
, storeFd
, storeFdWithName
, notifyWatchdog
, notifyReady
, notifyPID
, notifyErrno
, notifyStatus
, notifyBusError
, notifyReloading
, notifyStopping
, getActivatedSockets
, getActivatedSocketsWithNames
, unsetEnvironnement
) where
import Control.Applicative
import Control.Monad
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Maybe
import Data.List
import qualified Data.ByteString.Char8 as BC
import Foreign.C.Error (Errno (..))
import Foreign.C.Types (CInt (..))
import Foreign.Ptr
import System.Posix.Env
import System.Posix.Process
import System.Posix.Types (CPid (..))
import Data.ByteString.Unsafe (unsafeUseAsCStringLen)
import Network.Socket hiding (recv, recvFrom, send, sendTo)
import Network.Socket.ByteString
import Network.Socket.Internal (withSockAddr)
envVariableName :: String
envVariableName = "NOTIFY_SOCKET"
notifyWatchdog :: IO (Maybe())
notifyWatchdog = notify False "WATCHDOG=1"
notifyReady :: IO (Maybe())
notifyReady = notify False "READY=1"
notifyPID :: CPid -> IO (Maybe())
notifyPID pid = notify False $ "MAINPID=" ++ show pid
notifyReloading :: IO (Maybe())
notifyReloading = notify False "RELOADING=1"
notifyStopping :: IO (Maybe())
notifyStopping = notify False "STOPPING=1"
notifyErrno :: Errno -> IO (Maybe())
notifyErrno (Errno errorNb) = notify False $ "ERRNO=" ++ show errorNb
notifyStatus :: String -> IO (Maybe())
notifyStatus msg = notify False $ "STATUS=" ++ msg
notifyBusError :: String -> IO (Maybe())
notifyBusError msg = notify False $ "BUSERROR=" ++ msg
storeFd :: Bool -> Socket -> IO (Maybe ())
storeFd unset_env = notifyWithFD unset_env "FDSTORE=1"
storeFdWithName :: Bool -> Socket -> String -> IO (Maybe ())
storeFdWithName unset_env sock name = notifyWithFD unset_env ("FDSTORE=1\nFDNAME=" ++ name) sock
unsetEnvironnement :: IO ()
unsetEnvironnement = mapM_ unsetEnv [envVariableName, "LISTEN_PID", "LISTEN_FDS", "LISTEN_FDNAMES"]
notify :: Bool -> String -> IO (Maybe ())
notify unset_env state = notifyWithFD_ unset_env state Nothing
notifyWithFD :: Bool -> String -> Socket -> IO (Maybe ())
notifyWithFD unset_env state sock = notifyWithFD_ unset_env state (Just sock)
notifyWithFD_ :: Bool -> String -> Maybe Socket -> IO (Maybe ())
notifyWithFD_ unset_env state sock = do
res <- runMaybeT notifyImpl
when unset_env unsetEnvironnement
return res
where
isValidPath path = (length path >= 2)
&& ( "@" `isPrefixOf` path
|| "/" `isPrefixOf` path)
notifyImpl = do
guard $ state /= ""
socketPath <- MaybeT (getEnv envVariableName)
guard $ isValidPath socketPath
let socketPath' = if head socketPath == '@'
then '\0' : tail socketPath
else socketPath
socketFd <- liftIO $ socket AF_UNIX Datagram 0
nbBytes <- liftIO $ case sock of
Nothing -> sendTo socketFd (BC.pack state) (SockAddrUnix socketPath')
Just sock' -> sendBufWithFdTo socketFd (BC.pack state)
(SockAddrUnix socketPath') sock'
liftIO $ close socketFd
guard $ nbBytes >= length state
return ()
fdStart :: CInt
fdStart = 3
getActivatedSockets :: IO (Maybe [Socket])
getActivatedSockets = fmap (fmap fst) <$> getActivatedSocketsWithNames
getActivatedSocketsWithNames :: IO (Maybe [(Socket, String)])
getActivatedSocketsWithNames = runMaybeT $ do
listenPid <- read <$> MaybeT (getEnv "LISTEN_PID")
listenFDs <- read <$> MaybeT (getEnv "LISTEN_FDS")
listenFDNames <- MaybeT (getEnv "LISTEN_FDNAMES")
myPid <- liftIO getProcessID
guard $ listenPid == myPid
let listenFDNames' = fmap BC.unpack $ BC.split ':' $ BC.pack listenFDNames
sockets <- mapM makeSocket [fdStart .. fdStart + listenFDs 1]
guard $ length sockets == length listenFDNames'
return $ zip sockets listenFDNames'
where makeSocket :: CInt -> MaybeT IO Socket
makeSocket fd = do
fam <- socketFamily fd
typ <- socketType fd
stat <- socketStatus fd
liftIO $ mkSocket fd fam typ defaultProtocol stat
socketFamily :: CInt -> MaybeT IO Family
socketFamily fd = do
familyInt <- liftIO $ c_socket_family fd
guard $ familyInt >= 0
return $ unpackFamily familyInt
socketType :: CInt -> MaybeT IO SocketType
socketType fd = do
typeInt <- liftIO $ c_socket_type fd
case typeInt of
0 -> return NoSocketType
1 -> return Stream
2 -> return Datagram
3 -> return Raw
4 -> return RDM
5 -> return SeqPacket
_ -> mzero
socketStatus :: CInt -> MaybeT IO SocketStatus
socketStatus fd = do
listeningInt <- liftIO $ c_socket_listening fd
case listeningInt of
0 -> return Bound
1 -> return Listening
_ -> mzero
sendBufWithFdTo :: Socket -> BC.ByteString -> SockAddr -> Socket -> IO Int
sendBufWithFdTo sock state addr sockToSend =
unsafeUseAsCStringLen state $ \(ptr, nbytes) ->
withSockAddr addr $ \p_addr sz ->
fromIntegral <$> c_sd_notify_with_fd (fdSocket sock) ptr (fromIntegral nbytes)
p_addr (fromIntegral sz) (fdSocket sockToSend)
foreign import ccall unsafe "socket_family"
c_socket_family :: CInt -> IO CInt
foreign import ccall unsafe "socket_type"
c_socket_type :: CInt -> IO CInt
foreign import ccall unsafe "socket_listening"
c_socket_listening :: CInt -> IO CInt
foreign import ccall unsafe "sd_notify_with_fd"
c_sd_notify_with_fd :: CInt -> Ptr a -> CInt -> Ptr b -> CInt -> CInt -> IO CInt