-- | This module provides a simple interface to creating, checking the -- status of, and stopping background jobs. -- -- Use 'runDetached' to start a background job. For instance, here is -- a daemon that peridically hits a webserver: -- -- > import Control.Concurrent -- > import Control.Monad -- > import Data.Default -- > import Data.Maybe -- > import Network.BSD -- > import Network.HTTP -- > import Network.URI -- > import System.Posix.Daemon -- > -- > main :: IO () -- > main = runDetached (Just "diydns.pid") def $ forever $ do -- > hostname <- getHostName -- > _ <- simpleHTTP -- > (Request { rqURI = fromJust (parseURI "http://foo.com/dns") -- > , rqMethod = GET -- > , rqHeaders = [] -- > , rqBody = hostname }) -- > threadDelay (600 * 1000 * 1000) -- -- To check if the above job is running, use 'isRunning' with the same -- pidfile: -- -- > isRunning "diydns.pid" -- -- Finally, to stop the above job (maybe because we're rolling a new -- version of it), use 'kill': -- -- > kill "diydns.pid" -- -- To stop a job and wait for it to close (and release its pidfile), use -- 'killAndWait': -- -- > killAndWait "diydns.pid" >> doSomething -- -- As a side note, the code above is a script that the author uses as -- a sort of homebrew dynamic DNS: the remote address is a CGI script -- that records the IP addresses of all incoming requests in separate -- files named after the contents of the requests; the addresses are -- then viewable with any browser. module System.Posix.Daemon ( -- * Starting runDetached, Redirection(..), -- * Status isRunning, -- * Stopping kill, killAndWait, brutalKill ) where import Prelude hiding ( FilePath ) import Control.Monad ( when ) import Data.Default ( Default(..) ) import System.Directory ( doesFileExist ) import System.FilePath ( FilePath ) import System.IO ( SeekMode(..), hFlush, stdout ) import System.Posix.Files ( stdFileMode ) import System.Posix.IO ( openFd, OpenMode(..), defaultFileFlags, closeFd , dupTo, stdInput, stdOutput, stdError, getLock , createFile, fdWrite, fdRead , LockRequest (..), setLock, waitToSetLock ) import System.Posix.Process ( getProcessID, forkProcess, createSession ) import System.Posix.Signals ( Signal, signalProcess, sigQUIT, sigKILL ) -- | Where should the output (and input) of a daemon be redirected to? -- (we can't just leave it to the current terminal, because it may be -- closed, and that would kill the daemon). -- -- When in doubt, just use 'def', the default value. -- -- 'DevNull' causes the output to be redirected to @\/dev\/null@. This -- is safe and is what you want in most cases. -- -- If you don't want to lose the output (maybe because you're using it -- for logging), use 'ToFile', instead. data Redirection = DevNull | ToFile FilePath deriving ( Show ) instance Default Redirection where def = DevNull -- | Run the given action detached from the current terminal; this -- creates an entirely new process. This function returns -- immediately. Uses the double-fork technique to create a well -- behaved daemon. If @pidfile@ is given, check/write it; if we -- cannot obtain a lock on the file, another process is already using -- it, so fail. The @redirection@ parameter controls what to do with -- the standard channels (@stdin@, @stderr@, and @stdout@). -- -- See: <http://www.enderunix.org/docs/eng/daemon.php> -- -- Note: All unnecessary fds should be close before calling this. -- Otherwise, you get an fd leak. runDetached :: Maybe FilePath -- ^ pidfile -> Redirection -- ^ redirection -> IO () -- ^ program -> IO () runDetached maybePidFile redirection program = do -- check if the pidfile exists; fail if it does checkPidFile -- fork first child ignore $ forkProcess $ do -- create a new session and make this process its leader; see -- setsid(2) ignore $ createSession -- fork second child ignore $ forkProcess $ do -- create the pidfile writePidFile -- remap standard fds remapFds -- run the daemon program where ignore act = act >> return () -- Remap the standard channels based on the @redirection@ -- parameter. remapFds = do devnull <- openFd "/dev/null" ReadOnly Nothing defaultFileFlags ignore (dupTo devnull stdInput) closeFd devnull let file = case redirection of DevNull -> "/dev/null" ToFile filepath -> filepath fd <- openFd file ReadWrite (Just stdFileMode) defaultFileFlags hFlush stdout mapM_ (dupTo fd) [stdOutput, stdError] closeFd fd -- Convert the 'FilePath' @pidfile@ to a regular 'String' and run -- the action with it. withPidFile act = case maybePidFile of Nothing -> return () Just pidFile -> act pidFile -- Check if the pidfile exists; fail if it does, and create it, otherwise checkPidFile = withPidFile $ \pidFile -> do running <- isRunning pidFile when running $ fail "already running" writePidFile = withPidFile $ \pidFile -> do fd <- createFile pidFile stdFileMode setLock fd (WriteLock, AbsoluteSeek, 0, 0) pid <- getProcessID ignore $ fdWrite fd (show pid) -- note that we do not close the fd; doing so would release -- the lock -- | Return 'True' if the given file is locked by a process. In our -- case, returns 'True' when the daemon that created the file is still -- alive. isRunning :: FilePath -> IO Bool isRunning pidFile = do dfe <- doesFileExist pidFile if dfe then do fd <- openFd pidFile ReadWrite Nothing defaultFileFlags -- is there an *incompatible* lock on the pidfile? ml <- getLock fd (WriteLock, AbsoluteSeek, 0, 0) (pid, _) <- fdRead fd 100 closeFd fd case ml of Nothing -> do pid' <- getProcessID return (read pid == pid') Just _ -> do return True else do return False -- | Send 'sigQUIT' to the process recorded in the pidfile. This -- gives the process a chance to close cleanly. kill :: FilePath -> IO () kill = signalProcessByFilePath sigQUIT -- | Kill a process and wait for it to release its pidfile killAndWait :: FilePath -> IO () killAndWait pidFile = do signalProcessByFilePath sigQUIT pidFile fd <- openFd pidFile ReadWrite Nothing defaultFileFlags waitToSetLock fd (WriteLock, AbsoluteSeek, 0, 0) closeFd fd -- | Send 'sigKILL' to the process recorded in the pidfile. This -- immediately kills the process. brutalKill :: FilePath -> IO () brutalKill = signalProcessByFilePath sigKILL -- | Send a signal to a process whose pid is recorded in a file. signalProcessByFilePath :: Signal -> FilePath -> IO () signalProcessByFilePath signal pidFile = do pid <- readFile pidFile signalProcess signal (read pid)