git-annex/Utility/Daemon.hs

184 lines
4.9 KiB
Haskell
Raw Normal View History

{- daemon support
2012-06-11 04:39:09 +00:00
-
- Copyright 2012-2014 Joey Hess <id@joeyh.name>
2012-06-11 04:39:09 +00:00
-
- License: BSD-2-clause
2012-06-11 04:39:09 +00:00
-}
2013-05-10 21:29:59 +00:00
{-# LANGUAGE CPP #-}
2012-06-11 04:39:09 +00:00
module Utility.Daemon where
import Common
import Utility.PID
2013-08-04 17:54:09 +00:00
#ifndef mingw32_HOST_OS
import Utility.LogFile
#else
import Utility.WinProcess
2014-08-23 23:27:24 +00:00
import Utility.LockFile
2013-08-04 17:54:09 +00:00
#endif
#ifndef mingw32_HOST_OS
2012-06-11 04:39:09 +00:00
import System.Posix
import Control.Concurrent.Async
2013-05-11 22:23:41 +00:00
#endif
2012-06-11 04:39:09 +00:00
2014-02-11 17:18:59 +00:00
#ifndef mingw32_HOST_OS
2012-06-11 04:39:09 +00:00
{- Run an action as a daemon, with all output sent to a file descriptor.
-
- 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 ()
daemonize logfd pidfile changedirectory a = do
maybe noop checkalreadyrunning pidfile
_ <- forkProcess child1
out
2012-12-13 04:24:19 +00:00
where
2014-04-26 23:25:05 +00:00
checkalreadyrunning f = maybe noop (const alreadyRunning)
2012-12-13 04:24:19 +00:00
=<< checkDaemon f
child1 = do
2014-06-09 18:44:18 +00:00
_ <- tryIO createSession
2012-12-13 04:24:19 +00:00
_ <- forkProcess child2
out
child2 = do
maybe noop lockPidFile pidfile
when changedirectory $
setCurrentDirectory "/"
nullfd <- openFd "/dev/null" ReadOnly Nothing defaultFileFlags
redir nullfd stdInput
redirLog logfd
2014-06-09 17:48:44 +00:00
{- In old versions of ghc, 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
#endif
{- To run an action that is normally daemonized in the forground. -}
#ifndef mingw32_HOST_OS
foreground :: Fd -> Maybe FilePath -> IO () -> IO ()
foreground logfd pidfile a = do
#else
foreground :: Maybe FilePath -> IO () -> IO ()
foreground pidfile a = do
#endif
maybe noop lockPidFile pidfile
#ifndef mingw32_HOST_OS
2014-06-09 18:44:18 +00:00
_ <- tryIO createSession
redirLog logfd
#endif
a
#ifndef mingw32_HOST_OS
exitImmediately ExitSuccess
#else
exitWith ExitSuccess
2014-05-14 19:50:58 +00:00
#endif
{- Locks the pid file, with an exclusive, non-blocking lock,
- and leaves it locked on return.
-
- Writes the pid to the file, fully atomically.
- Fails if the pid file is already locked by another process. -}
lockPidFile :: FilePath -> IO ()
lockPidFile pidfile = do
createDirectoryIfMissing True (parentDir pidfile)
#ifndef mingw32_HOST_OS
fd <- openFd pidfile ReadWrite (Just stdFileMode) defaultFileFlags
2012-07-02 17:47:32 +00:00
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
(Nothing, _) -> alreadyRunning
(_, Nothing) -> alreadyRunning
2012-07-02 17:47:32 +00:00
_ -> do
_ <- fdWrite fd' =<< show <$> getPID
2012-07-02 17:47:32 +00:00
closeFd fd
rename newfile pidfile
where
newfile = pidfile ++ ".new"
#else
{- Not atomic on Windows, oh well. -}
unlessM (isNothing <$> checkDaemon pidfile)
alreadyRunning
pid <- getPID
writeFile pidfile (show pid)
lckfile <- winLockFile pid pidfile
writeFile lckfile ""
void $ lockExclusive lckfile
#endif
alreadyRunning :: IO ()
alreadyRunning = error "Daemon is already running."
2012-06-11 06:01:20 +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
-
- If it's running, returns its pid. -}
checkDaemon :: FilePath -> IO (Maybe PID)
#ifndef mingw32_HOST_OS
checkDaemon pidfile = bracket setup cleanup go
2012-12-13 04:24:19 +00:00
where
setup = catchMaybeIO $
openFd pidfile ReadOnly (Just stdFileMode) defaultFileFlags
cleanup (Just fd) = closeFd fd
cleanup Nothing = return ()
go (Just fd) = catchDefaultIO Nothing $ do
locked <- getLock fd (ReadLock, AbsoluteSeek, 0, 0)
p <- readish <$> readFile pidfile
return (check locked p)
go Nothing = return Nothing
2012-12-13 04:24:19 +00:00
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 ++ " )"
#else
checkDaemon pidfile = maybe (return Nothing) (check . readish)
=<< catchMaybeIO (readFile pidfile)
where
check Nothing = return Nothing
check (Just pid) = do
v <- lockShared =<< winLockFile pid pidfile
case v of
Just h -> do
dropLock h
return Nothing
Nothing -> return (Just pid)
#endif
{- Stops the daemon, safely. -}
stopDaemon :: FilePath -> IO ()
stopDaemon pidfile = go =<< checkDaemon pidfile
2012-12-13 04:24:19 +00:00
where
go Nothing = noop
go (Just pid) =
#ifndef mingw32_HOST_OS
signalProcess sigTERM pid
#else
terminatePID pid
#endif
{- Windows locks a lock file that corresponds with the pid of the process.
- This allows changing the process in the pid file and taking a new lock
- when eg, restarting the daemon.
-}
#ifdef mingw32_HOST_OS
winLockFile :: PID -> FilePath -> IO FilePath
winLockFile pid pidfile = do
cleanstale
return $ prefix ++ show pid ++ suffix
where
prefix = pidfile ++ "."
suffix = ".lck"
cleanstale = mapM_ (void . tryIO . removeFile) =<<
(filter iswinlockfile <$> dirContents (parentDir pidfile))
iswinlockfile f = suffix `isSuffixOf` f && prefix `isPrefixOf` f
#endif