{-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} -- | Main entry point of the library module System.Restricted.Worker ( -- * Exposed modules module System.Restricted.Worker.Types , module System.Restricted.Worker.Pool , module System.Restricted.Worker.Protocol -- * Creating workers , mkDefaultWorker , startWorker , startIOWorker -- * Quering and killing workers , killWorker , workerAlive , connectToWorker ) where import Prelude hiding (mapM_) import Control.Monad (forever) import Control.Monad.Base (MonadBase (..)) import Control.Monad.IO.Class (MonadIO, liftIO) import Data.Foldable (mapM_) import Data.Typeable () import Network (Socket, accept) import System.IO (Handle) import System.Posix.User (getEffectiveUserID, setEffectiveUserID) import System.Restricted.Limits import System.Restricted.Types import System.Restricted.Worker.Internal import System.Restricted.Worker.Pool import System.Restricted.Worker.Protocol import System.Restricted.Worker.Types -- | Create an uninitialized worker mkDefaultWorker :: String -> FilePath -> LimitSettings -> Worker a mkDefaultWorker name sock set = Worker { workerName = name , workerSocket = sock , workerLimits = set , workerPid = Nothing } {-| Start a general type of worker. The pre-forking action is a monadic action that will be run prior to calling 'forkWorker'. It might be some initialization code, running the DB query, anything you want. The resulting 'WData' will be passed to the callback. The socket that is passed to the callback is a server socket. -} startWorker :: (WorkerData w, MonadIO (WMonad w), MonadBase (WMonad w) m) => String -- ^ Name -> FilePath -- ^ Socket -> Maybe (IO Handle) -- ^ Where to redirect stdout, stderr -> LimitSettings -- ^ Restrictions -> WMonad w (WData w) -- ^ Pre-forking action -> (WData w -> Socket -> IO ()) -- ^ Socket callback -> WMonad w (Worker w, RestartWorker m w) startWorker name sock out set pre cb = do let defW = mkDefaultWorker name sock set let restarter !w = do w' <- liftIO $ killWorker w oldId <- liftIO $ getEffectiveUserID liftIO $ mapM_ setEffectiveUserID (processUid set) -- this is necessary so that the control socket is accessible by -- non-root processes, probably a hack dat <- pre pid <- liftIO $ forkWorker w' out (cb dat) liftIO $ setEffectiveUserID oldId liftIO $ setCGroup set pid let w'' = w' { workerPid = Just pid } w'' `seq` return w'' w' <- restarter defW return (w', liftBase . restarter) -- | Start a worker of type 'IOWorker' -- The callback function is called every time a connectino is established -- -- >>> startIOWorker "test" "/tmp/test.sock" $ \h -> hPutStrLn h "hello, world" -- startIOWorker :: String -- ^ Name -> LimitSettings -- ^ Restrictions -> FilePath -- ^ UNIX socket -> (Handle -> IO ()) -- ^ Callback -> IO (Worker IOWorker, RestartWorker IO IOWorker) startIOWorker name set sock callb = startWorker name sock out set preFork handle where handle () soc = forever $ do (hndl, _, _) <- accept soc callb hndl out = Nothing preFork = putStrLn ("Starting worker " ++ show name) >> return ()