2012-06-11 05:20:19 +00:00
|
|
|
{- daemon support
|
2012-06-11 04:39:09 +00:00
|
|
|
-
|
|
|
|
- Copyright 2012 Joey Hess <joey@kitenet.net>
|
|
|
|
-
|
|
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
2013-05-10 21:29:59 +00:00
|
|
|
{-# LANGUAGE CPP #-}
|
|
|
|
|
2012-06-11 04:39:09 +00:00
|
|
|
module Utility.Daemon where
|
|
|
|
|
2012-06-11 05:20:19 +00:00
|
|
|
import Common
|
2013-08-04 17:54:09 +00:00
|
|
|
#ifndef mingw32_HOST_OS
|
2013-03-01 17:30:48 +00:00
|
|
|
import Utility.LogFile
|
2013-08-04 17:54:09 +00:00
|
|
|
#endif
|
2012-06-11 05:20:19 +00:00
|
|
|
|
2013-08-02 16:27:32 +00:00
|
|
|
#ifndef mingw32_HOST_OS
|
2012-06-11 04:39:09 +00:00
|
|
|
import System.Posix
|
2013-10-10 20:05:44 +00:00
|
|
|
import Control.Concurrent.Async
|
2013-05-11 22:23:41 +00:00
|
|
|
#else
|
2013-05-14 21:32:03 +00:00
|
|
|
import System.PosixCompat
|
2013-05-11 22:23:41 +00:00
|
|
|
#endif
|
2012-06-11 04:39:09 +00:00
|
|
|
|
|
|
|
{- Run an action as a daemon, with all output sent to a file descriptor.
|
|
|
|
-
|
2012-06-11 05:20:19 +00:00
|
|
|
- Can write its pid to a file, to guard against multiple instances
|
|
|
|
- running and allow easy termination.
|
|
|
|
-
|
|
|
|
- When successful, does not return. -}
|
|
|
|
daemonize :: Fd -> Maybe FilePath -> Bool -> IO () -> IO ()
|
2013-08-02 16:27:32 +00:00
|
|
|
#ifndef mingw32_HOST_OS
|
2012-06-11 05:20:19 +00:00
|
|
|
daemonize logfd pidfile changedirectory a = do
|
2012-11-29 19:44:46 +00:00
|
|
|
maybe noop checkalreadyrunning pidfile
|
2012-06-11 05:37:25 +00:00
|
|
|
_ <- forkProcess child1
|
2012-06-11 05:20:19 +00:00
|
|
|
out
|
2012-12-13 04:24:19 +00:00
|
|
|
where
|
|
|
|
checkalreadyrunning f = maybe noop (const $ alreadyRunning)
|
|
|
|
=<< checkDaemon f
|
|
|
|
child1 = do
|
|
|
|
_ <- createSession
|
|
|
|
_ <- forkProcess child2
|
|
|
|
out
|
|
|
|
child2 = do
|
|
|
|
maybe noop lockPidFile pidfile
|
|
|
|
when changedirectory $
|
|
|
|
setCurrentDirectory "/"
|
|
|
|
nullfd <- openFd "/dev/null" ReadOnly Nothing defaultFileFlags
|
2013-01-15 17:34:59 +00:00
|
|
|
redir nullfd stdInput
|
|
|
|
redirLog logfd
|
2013-10-10 20:05:44 +00:00
|
|
|
{- forkProcess masks async exceptions; unmask them inside
|
|
|
|
- the action. -}
|
|
|
|
wait =<< asyncWithUnmask (\unmask -> unmask a)
|
2012-12-13 04:24:19 +00:00
|
|
|
out
|
|
|
|
out = exitImmediately ExitSuccess
|
2013-05-11 20:03:00 +00:00
|
|
|
#else
|
2013-05-14 21:32:03 +00:00
|
|
|
daemonize = error "daemonize is not implemented on Windows" -- TODO
|
2013-05-11 20:03:00 +00:00
|
|
|
#endif
|
2012-06-11 05:37:25 +00:00
|
|
|
|
2012-07-02 17:47:32 +00:00
|
|
|
{- Locks the pid file, with an exclusive, non-blocking lock.
|
2012-08-01 20:29:38 +00:00
|
|
|
- Writes the pid to the file, fully atomically.
|
|
|
|
- Fails if the pid file is already locked by another process. -}
|
|
|
|
lockPidFile :: FilePath -> IO ()
|
|
|
|
lockPidFile file = do
|
2012-08-08 18:18:02 +00:00
|
|
|
createDirectoryIfMissing True (parentDir file)
|
2013-08-02 16:27:32 +00:00
|
|
|
#ifndef mingw32_HOST_OS
|
2012-07-02 17:47:32 +00:00
|
|
|
fd <- openFd file ReadWrite (Just stdFileMode) defaultFileFlags
|
|
|
|
locked <- catchMaybeIO $ setLock fd (WriteLock, AbsoluteSeek, 0, 0)
|
|
|
|
fd' <- openFd newfile ReadWrite (Just stdFileMode) defaultFileFlags
|
|
|
|
{ trunc = True }
|
|
|
|
locked' <- catchMaybeIO $ setLock fd' (WriteLock, AbsoluteSeek, 0, 0)
|
|
|
|
case (locked, locked') of
|
2012-11-29 19:44:46 +00:00
|
|
|
(Nothing, _) -> alreadyRunning
|
|
|
|
(_, Nothing) -> alreadyRunning
|
2012-07-02 17:47:32 +00:00
|
|
|
_ -> do
|
|
|
|
_ <- fdWrite fd' =<< show <$> getProcessID
|
|
|
|
closeFd fd
|
2013-05-11 20:03:00 +00:00
|
|
|
#else
|
2013-05-14 21:32:03 +00:00
|
|
|
writeFile newfile "-1"
|
2013-05-11 20:03:00 +00:00
|
|
|
#endif
|
2013-05-14 21:32:03 +00:00
|
|
|
renameFile newfile file
|
|
|
|
where
|
|
|
|
newfile = file ++ ".new"
|
2012-11-29 19:44:46 +00:00
|
|
|
|
|
|
|
alreadyRunning :: IO ()
|
|
|
|
alreadyRunning = error "Daemon is already running."
|
2012-06-11 06:01:20 +00:00
|
|
|
|
2012-07-26 03:13:01 +00:00
|
|
|
{- Checks if the daemon is running, by checking that the pid file
|
|
|
|
- is locked by the same process that is listed in the pid file.
|
2012-06-11 06:01:20 +00:00
|
|
|
-
|
2012-07-26 03:13:01 +00:00
|
|
|
- If it's running, returns its pid. -}
|
|
|
|
checkDaemon :: FilePath -> IO (Maybe ProcessID)
|
2013-08-02 16:27:32 +00:00
|
|
|
#ifndef mingw32_HOST_OS
|
2012-07-26 03:13:01 +00:00
|
|
|
checkDaemon pidfile = do
|
|
|
|
v <- catchMaybeIO $
|
|
|
|
openFd pidfile ReadOnly (Just stdFileMode) defaultFileFlags
|
|
|
|
case v of
|
|
|
|
Just fd -> do
|
|
|
|
locked <- getLock fd (ReadLock, AbsoluteSeek, 0, 0)
|
|
|
|
p <- readish <$> readFile pidfile
|
2013-05-27 20:48:41 +00:00
|
|
|
closeFd fd `after` return (check locked p)
|
2012-07-26 03:13:01 +00:00
|
|
|
Nothing -> return Nothing
|
2012-12-13 04:24:19 +00:00
|
|
|
where
|
|
|
|
check Nothing _ = Nothing
|
|
|
|
check _ Nothing = Nothing
|
|
|
|
check (Just (pid, _)) (Just pid')
|
|
|
|
| pid == pid' = Just pid
|
|
|
|
| otherwise = error $
|
|
|
|
"stale pid in " ++ pidfile ++
|
|
|
|
" (got " ++ show pid' ++
|
|
|
|
"; expected " ++ show pid ++ " )"
|
2013-05-11 20:03:00 +00:00
|
|
|
#else
|
2013-05-14 21:32:03 +00:00
|
|
|
checkDaemon pidfile = maybe Nothing readish <$> catchMaybeIO (readFile pidfile)
|
2013-05-11 20:03:00 +00:00
|
|
|
#endif
|
2012-07-26 03:13:01 +00:00
|
|
|
|
|
|
|
{- Stops the daemon, safely. -}
|
|
|
|
stopDaemon :: FilePath -> IO ()
|
2013-08-02 16:27:32 +00:00
|
|
|
#ifndef mingw32_HOST_OS
|
2012-07-26 03:13:01 +00:00
|
|
|
stopDaemon pidfile = go =<< checkDaemon pidfile
|
2012-12-13 04:24:19 +00:00
|
|
|
where
|
|
|
|
go Nothing = noop
|
|
|
|
go (Just pid) = signalProcess sigTERM pid
|
2013-05-11 20:03:00 +00:00
|
|
|
#else
|
2013-05-14 21:32:03 +00:00
|
|
|
stopDaemon = error "stopDaemon is not implemented on Windows" -- TODO
|
2013-05-11 20:03:00 +00:00
|
|
|
#endif
|