git-annex/Utility/Daemon.hs

195 lines
5.4 KiB
Haskell
Raw Permalink Normal View History

{- daemon support
2012-06-11 04:39:09 +00:00
-
- Copyright 2012-2021 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 #-}
module Utility.Daemon (
2020-01-01 17:05:23 +00:00
#ifndef mingw32_HOST_OS
daemonize,
2020-01-01 17:05:23 +00:00
#endif
foreground,
checkDaemon,
stopDaemon,
) where
2012-06-11 04:39:09 +00:00
import Common
import Utility.PID
2013-08-04 17:54:09 +00:00
#ifndef mingw32_HOST_OS
import Utility.LogFile
import Utility.Env
import Utility.OpenFd
#else
import System.Win32.Process (terminateProcessById)
2014-08-23 23:27:24 +00:00
import Utility.LockFile
2013-08-04 17:54:09 +00:00
#endif
#ifndef mingw32_HOST_OS
import System.Posix hiding (getEnv, getEnvironment)
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
{- Run an action as a daemon, with all output sent to a file descriptor,
- and in a new session.
2012-06-11 04:39:09 +00:00
-
- Can write its pid to a file.
-
- This does not double-fork to background, because forkProcess is
- rather fragile and highly unused in haskell programs, so likely to break.
- Instead, it runs the cmd with provided params, in the background,
- which the caller should arrange to run this again.
-}
daemonize :: String -> [CommandParam] -> IO Fd -> Maybe FilePath -> Bool -> IO () -> IO ()
daemonize cmd params openlogfd pidfile changedirectory a = do
maybe noop checkalreadyrunning pidfile
getEnv envvar >>= \case
Just s | s == cmd -> do
maybe noop lockPidFile pidfile
a
_ -> do
nullfd <- openFdWithMode (toRawFilePath "/dev/null") ReadOnly Nothing defaultFileFlags
redir nullfd stdInput
redirLog =<< openlogfd
environ <- getEnvironment
_ <- createProcess $
(proc cmd (toCommand params))
{ env = Just (addEntry envvar cmd environ)
, create_group = True
, new_session = True
, cwd = if changedirectory then Just "/" else Nothing
}
return ()
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
envvar = "DAEMONIZED"
#endif
2023-03-14 02:39:16 +00:00
{- To run an action that is normally daemonized in the foreground. -}
#ifndef mingw32_HOST_OS
foreground :: IO Fd -> Maybe FilePath -> IO () -> IO ()
foreground openlogfd 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 =<< openlogfd
#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
#ifndef mingw32_HOST_OS
fd <- openFdWithMode (toRawFilePath pidfile) ReadWrite (Just stdFileMode) defaultFileFlags
2012-07-02 17:47:32 +00:00
locked <- catchMaybeIO $ setLock fd (WriteLock, AbsoluteSeek, 0, 0)
fd' <- openFdWithMode (toRawFilePath newfile) ReadWrite (Just stdFileMode) defaultFileFlags
2012-07-02 17:47:32 +00:00
{ 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 (fromRawFilePath lckfile) ""
void $ lockExclusive lckfile
#endif
alreadyRunning :: IO ()
alreadyRunning = giveup "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 $
openFdWithMode (toRawFilePath 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 = giveup $
2012-12-13 04:24:19 +00:00
"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
terminateProcessById 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
2020-11-17 15:58:45 +00:00
winLockFile :: PID -> FilePath -> IO RawFilePath
winLockFile pid pidfile = do
cleanstale
2020-11-17 15:58:45 +00:00
return $ toRawFilePath $ prefix ++ show pid ++ suffix
where
prefix = pidfile ++ "."
suffix = ".lck"
cleanstale = mapM_ (void . tryIO . removeFile) =<<
2020-11-17 15:58:45 +00:00
(filter iswinlockfile <$> dirContents (fromRawFilePath (parentDir (toRawFilePath pidfile))))
iswinlockfile f = suffix `isSuffixOf` f && prefix `isPrefixOf` f
#endif