2012-06-11 01:20:19 -04:00
|
|
|
{- daemon support
|
2012-06-11 00:39:09 -04:00
|
|
|
-
|
2021-05-12 15:08:03 -04:00
|
|
|
- Copyright 2012-2021 Joey Hess <id@joeyh.name>
|
2012-06-11 00:39:09 -04:00
|
|
|
-
|
2014-05-10 11:01:27 -03:00
|
|
|
- License: BSD-2-clause
|
2012-06-11 00:39:09 -04:00
|
|
|
-}
|
|
|
|
|
2013-05-10 16:29:59 -05:00
|
|
|
{-# LANGUAGE CPP #-}
|
|
|
|
|
2019-11-21 15:38:06 -04:00
|
|
|
module Utility.Daemon (
|
2020-01-01 13:05:23 -04:00
|
|
|
#ifndef mingw32_HOST_OS
|
2019-11-21 15:38:06 -04:00
|
|
|
daemonize,
|
2020-01-01 13:05:23 -04:00
|
|
|
#endif
|
2019-11-21 15:38:06 -04:00
|
|
|
foreground,
|
|
|
|
checkDaemon,
|
|
|
|
stopDaemon,
|
|
|
|
) where
|
2012-06-11 00:39:09 -04:00
|
|
|
|
2012-06-11 01:20:19 -04:00
|
|
|
import Common
|
2014-02-11 15:22:08 -04:00
|
|
|
import Utility.PID
|
2013-08-04 13:54:09 -04:00
|
|
|
#ifndef mingw32_HOST_OS
|
2013-03-01 13:30:48 -04:00
|
|
|
import Utility.LogFile
|
2021-05-12 15:08:03 -04:00
|
|
|
import Utility.Env
|
2023-08-01 18:41:27 -04:00
|
|
|
import Utility.OpenFd
|
2014-02-13 14:00:15 -04:00
|
|
|
#else
|
2017-10-25 19:46:28 -04:00
|
|
|
import System.Win32.Process (terminateProcessById)
|
2014-08-23 16:27:24 -07:00
|
|
|
import Utility.LockFile
|
2013-08-04 13:54:09 -04:00
|
|
|
#endif
|
2012-06-11 01:20:19 -04:00
|
|
|
|
2013-08-02 12:27:32 -04:00
|
|
|
#ifndef mingw32_HOST_OS
|
2021-05-12 15:08:03 -04:00
|
|
|
import System.Posix hiding (getEnv, getEnvironment)
|
2013-05-11 18:23:41 -04:00
|
|
|
#endif
|
2012-06-11 00:39:09 -04:00
|
|
|
|
2014-02-11 13:18:59 -04:00
|
|
|
#ifndef mingw32_HOST_OS
|
2021-05-12 15:08:03 -04:00
|
|
|
{- Run an action as a daemon, with all output sent to a file descriptor,
|
|
|
|
- and in a new session.
|
2012-06-11 00:39:09 -04:00
|
|
|
-
|
2021-05-12 15:08:03 -04:00
|
|
|
- Can write its pid to a file.
|
2012-06-11 01:20:19 -04:00
|
|
|
-
|
2021-05-12 15:08:03 -04:00
|
|
|
- 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
|
2012-11-29 15:44:46 -04:00
|
|
|
maybe noop checkalreadyrunning pidfile
|
2021-05-12 15:08:03 -04:00
|
|
|
getEnv envvar >>= \case
|
|
|
|
Just s | s == cmd -> do
|
|
|
|
maybe noop lockPidFile pidfile
|
|
|
|
a
|
|
|
|
_ -> do
|
2023-08-01 18:41:27 -04:00
|
|
|
nullfd <- openFdWithMode (toRawFilePath "/dev/null") ReadOnly Nothing defaultFileFlags
|
2021-05-12 15:08:03 -04:00
|
|
|
redir nullfd stdInput
|
|
|
|
redirLog =<< openlogfd
|
|
|
|
environ <- getEnvironment
|
|
|
|
_ <- createProcess $
|
|
|
|
(proc cmd (toCommand params))
|
|
|
|
{ env = Just (addEntry envvar cmd environ)
|
|
|
|
, create_group = True
|
|
|
|
, new_session = True
|
2021-05-12 17:44:22 -04:00
|
|
|
, cwd = if changedirectory then Just "/" else Nothing
|
2021-05-12 15:08:03 -04:00
|
|
|
}
|
|
|
|
return ()
|
2012-12-13 00:24:19 -04:00
|
|
|
where
|
2014-04-26 19:25:05 -04:00
|
|
|
checkalreadyrunning f = maybe noop (const alreadyRunning)
|
2012-12-13 00:24:19 -04:00
|
|
|
=<< checkDaemon f
|
2021-05-12 15:08:03 -04:00
|
|
|
envvar = "DAEMONIZED"
|
2014-07-14 15:44:44 -04:00
|
|
|
#endif
|
2012-06-11 01:37:25 -04:00
|
|
|
|
2023-03-13 22:39:16 -04:00
|
|
|
{- To run an action that is normally daemonized in the foreground. -}
|
2014-07-14 15:44:44 -04:00
|
|
|
#ifndef mingw32_HOST_OS
|
2021-05-12 15:08:03 -04:00
|
|
|
foreground :: IO Fd -> Maybe FilePath -> IO () -> IO ()
|
|
|
|
foreground openlogfd pidfile a = do
|
2014-07-14 15:44:44 -04:00
|
|
|
#else
|
|
|
|
foreground :: Maybe FilePath -> IO () -> IO ()
|
|
|
|
foreground pidfile a = do
|
|
|
|
#endif
|
2014-05-14 14:26:28 -04:00
|
|
|
maybe noop lockPidFile pidfile
|
2014-07-14 15:44:44 -04:00
|
|
|
#ifndef mingw32_HOST_OS
|
2014-06-09 14:44:18 -04:00
|
|
|
_ <- tryIO createSession
|
2021-05-12 15:08:03 -04:00
|
|
|
redirLog =<< openlogfd
|
2014-07-14 15:44:44 -04:00
|
|
|
#endif
|
2014-05-14 14:26:28 -04:00
|
|
|
a
|
2014-07-14 15:44:44 -04:00
|
|
|
#ifndef mingw32_HOST_OS
|
2014-05-14 14:26:28 -04:00
|
|
|
exitImmediately ExitSuccess
|
2014-07-14 15:44:44 -04:00
|
|
|
#else
|
|
|
|
exitWith ExitSuccess
|
2014-05-14 15:50:58 -04:00
|
|
|
#endif
|
2014-05-14 14:26:28 -04:00
|
|
|
|
2014-02-13 16:03:49 -04:00
|
|
|
{- Locks the pid file, with an exclusive, non-blocking lock,
|
|
|
|
- and leaves it locked on return.
|
|
|
|
-
|
2012-08-01 16:29:38 -04:00
|
|
|
- Writes the pid to the file, fully atomically.
|
|
|
|
- Fails if the pid file is already locked by another process. -}
|
|
|
|
lockPidFile :: FilePath -> IO ()
|
2014-02-13 16:03:49 -04:00
|
|
|
lockPidFile pidfile = do
|
2013-08-02 12:27:32 -04:00
|
|
|
#ifndef mingw32_HOST_OS
|
2023-08-01 18:41:27 -04:00
|
|
|
fd <- openFdWithMode (toRawFilePath pidfile) ReadWrite (Just stdFileMode) defaultFileFlags
|
2012-07-02 13:47:32 -04:00
|
|
|
locked <- catchMaybeIO $ setLock fd (WriteLock, AbsoluteSeek, 0, 0)
|
2023-08-01 18:41:27 -04:00
|
|
|
fd' <- openFdWithMode (toRawFilePath newfile) ReadWrite (Just stdFileMode) defaultFileFlags
|
2012-07-02 13:47:32 -04:00
|
|
|
{ trunc = True }
|
|
|
|
locked' <- catchMaybeIO $ setLock fd' (WriteLock, AbsoluteSeek, 0, 0)
|
|
|
|
case (locked, locked') of
|
2012-11-29 15:44:46 -04:00
|
|
|
(Nothing, _) -> alreadyRunning
|
|
|
|
(_, Nothing) -> alreadyRunning
|
2012-07-02 13:47:32 -04:00
|
|
|
_ -> do
|
2014-02-11 15:22:08 -04:00
|
|
|
_ <- fdWrite fd' =<< show <$> getPID
|
2012-07-02 13:47:32 -04:00
|
|
|
closeFd fd
|
2014-02-13 16:03:49 -04:00
|
|
|
rename newfile pidfile
|
|
|
|
where
|
|
|
|
newfile = pidfile ++ ".new"
|
2013-05-11 15:03:00 -05:00
|
|
|
#else
|
2014-02-13 16:03:49 -04:00
|
|
|
{- Not atomic on Windows, oh well. -}
|
2014-02-13 17:37:49 -04:00
|
|
|
unlessM (isNothing <$> checkDaemon pidfile)
|
|
|
|
alreadyRunning
|
2014-02-13 16:03:49 -04:00
|
|
|
pid <- getPID
|
|
|
|
writeFile pidfile (show pid)
|
|
|
|
lckfile <- winLockFile pid pidfile
|
2020-11-19 12:33:00 -04:00
|
|
|
writeFile (fromRawFilePath lckfile) ""
|
2014-02-13 16:03:49 -04:00
|
|
|
void $ lockExclusive lckfile
|
2013-05-11 15:03:00 -05:00
|
|
|
#endif
|
2012-11-29 15:44:46 -04:00
|
|
|
|
|
|
|
alreadyRunning :: IO ()
|
2016-11-15 21:29:54 -04:00
|
|
|
alreadyRunning = giveup "Daemon is already running."
|
2012-06-11 02:01:20 -04:00
|
|
|
|
2012-07-25 23:13:01 -04: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 02:01:20 -04:00
|
|
|
-
|
2012-07-25 23:13:01 -04:00
|
|
|
- If it's running, returns its pid. -}
|
2014-02-11 15:22:08 -04:00
|
|
|
checkDaemon :: FilePath -> IO (Maybe PID)
|
2013-08-02 12:27:32 -04:00
|
|
|
#ifndef mingw32_HOST_OS
|
2015-11-16 14:27:23 -04:00
|
|
|
checkDaemon pidfile = bracket setup cleanup go
|
2012-12-13 00:24:19 -04:00
|
|
|
where
|
2015-11-16 14:27:23 -04:00
|
|
|
setup = catchMaybeIO $
|
2023-08-01 18:41:27 -04:00
|
|
|
openFdWithMode (toRawFilePath pidfile) ReadOnly (Just stdFileMode) defaultFileFlags
|
2015-11-16 14:27:23 -04:00
|
|
|
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 00:24:19 -04:00
|
|
|
check Nothing _ = Nothing
|
|
|
|
check _ Nothing = Nothing
|
|
|
|
check (Just (pid, _)) (Just pid')
|
|
|
|
| pid == pid' = Just pid
|
2016-11-15 21:29:54 -04:00
|
|
|
| otherwise = giveup $
|
2012-12-13 00:24:19 -04:00
|
|
|
"stale pid in " ++ pidfile ++
|
|
|
|
" (got " ++ show pid' ++
|
|
|
|
"; expected " ++ show pid ++ " )"
|
2013-05-11 15:03:00 -05:00
|
|
|
#else
|
2014-02-13 16:03:49 -04:00
|
|
|
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)
|
2013-05-11 15:03:00 -05:00
|
|
|
#endif
|
2012-07-25 23:13:01 -04:00
|
|
|
|
|
|
|
{- Stops the daemon, safely. -}
|
|
|
|
stopDaemon :: FilePath -> IO ()
|
|
|
|
stopDaemon pidfile = go =<< checkDaemon pidfile
|
2012-12-13 00:24:19 -04:00
|
|
|
where
|
|
|
|
go Nothing = noop
|
2014-02-11 15:22:08 -04:00
|
|
|
go (Just pid) =
|
|
|
|
#ifndef mingw32_HOST_OS
|
|
|
|
signalProcess sigTERM pid
|
|
|
|
#else
|
2017-10-25 19:46:28 -04:00
|
|
|
terminateProcessById pid
|
2013-05-11 15:03:00 -05:00
|
|
|
#endif
|
2014-02-13 16:03:49 -04:00
|
|
|
|
|
|
|
{- 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 11:58:45 -04:00
|
|
|
winLockFile :: PID -> FilePath -> IO RawFilePath
|
2014-02-13 16:03:49 -04:00
|
|
|
winLockFile pid pidfile = do
|
|
|
|
cleanstale
|
2020-11-17 11:58:45 -04:00
|
|
|
return $ toRawFilePath $ prefix ++ show pid ++ suffix
|
2014-02-13 16:03:49 -04:00
|
|
|
where
|
2014-10-09 14:53:13 -04:00
|
|
|
prefix = pidfile ++ "."
|
2014-02-13 16:03:49 -04:00
|
|
|
suffix = ".lck"
|
|
|
|
cleanstale = mapM_ (void . tryIO . removeFile) =<<
|
2020-11-17 11:58:45 -04:00
|
|
|
(filter iswinlockfile <$> dirContents (fromRawFilePath (parentDir (toRawFilePath pidfile))))
|
2014-02-13 16:03:49 -04:00
|
|
|
iswinlockfile f = suffix `isSuffixOf` f && prefix `isPrefixOf` f
|
|
|
|
#endif
|