Commit 6ec7a50e authored by Iustin Pop's avatar Iustin Pop
Browse files

htools: add basic daemon-related functionality



This is not complete for now, just the basic functionality has been
implemented:

- daemonize
- check we're running under the correct user
- call setup logging
Signed-off-by: default avatarIustin Pop <iustin@google.com>
Reviewed-by: default avatarGuido Trotter <ultrotter@google.com>
parent ba4e38e8
......@@ -397,6 +397,7 @@ HS_LIB_SRCS = \
htools/Ganeti/BasicTypes.hs \
htools/Ganeti/Confd.hs \
htools/Ganeti/Config.hs \
htools/Ganeti/Daemon.hs \
htools/Ganeti/Hash.hs \
htools/Ganeti/Jobs.hs \
htools/Ganeti/Logging.hs \
......
{-| Implementation of the generic daemon functionality.
-}
{-
Copyright (C) 2011, 2012 Google Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
-}
module Ganeti.Daemon
( DaemonOptions(..)
, OptType
, defaultOptions
, oShowHelp
, oShowVer
, oNoDaemonize
, oNoUserChecks
, oDebug
, oPort
, parseArgs
, writePidFile
, genericMain
) where
import Control.Monad
import qualified Data.Version
import Data.Word
import System.Console.GetOpt
import System.Exit
import System.Environment
import System.Info
import System.IO
import System.Posix.Directory
import System.Posix.Files
import System.Posix.IO
import System.Posix.Process
import System.Posix.Types
import Text.Printf
import Ganeti.Logging
import Ganeti.Runtime
import Ganeti.BasicTypes
import Ganeti.HTools.Utils
import qualified Ganeti.HTools.Version as Version(version)
import qualified Ganeti.Constants as C
-- * Data types
-- | Command line options structure.
data DaemonOptions = DaemonOptions
{ optShowHelp :: Bool -- ^ Just show the help
, optShowVer :: Bool -- ^ Just show the program version
, optDaemonize :: Bool -- ^ Whether to daemonize or not
, optPort :: Maybe Word16 -- ^ Override for the network port
, optDebug :: Bool -- ^ Enable debug messages
, optNoUserChecks :: Bool -- ^ Ignore user checks
}
-- | Default values for the command line options.
defaultOptions :: DaemonOptions
defaultOptions = DaemonOptions
{ optShowHelp = False
, optShowVer = False
, optDaemonize = True
, optPort = Nothing
, optDebug = False
, optNoUserChecks = False
}
-- | Abrreviation for the option type.
type OptType = OptDescr (DaemonOptions -> Result DaemonOptions)
-- | Helper function for required arguments which need to be converted
-- as opposed to stored just as string.
reqWithConversion :: (String -> Result a)
-> (a -> DaemonOptions -> Result DaemonOptions)
-> String
-> ArgDescr (DaemonOptions -> Result DaemonOptions)
reqWithConversion conversion_fn updater_fn metavar =
ReqArg (\string_opt opts -> do
parsed_value <- conversion_fn string_opt
updater_fn parsed_value opts) metavar
-- * Command line options
oShowHelp :: OptType
oShowHelp = Option "h" ["help"]
(NoArg (\ opts -> Ok opts { optShowHelp = True}))
"Show the help message and exit"
oShowVer :: OptType
oShowVer = Option "V" ["version"]
(NoArg (\ opts -> Ok opts { optShowVer = True}))
"Show the version of the program and exit"
oNoDaemonize :: OptType
oNoDaemonize = Option "f" ["foreground"]
(NoArg (\ opts -> Ok opts { optDaemonize = False}))
"Don't detach from the current terminal"
oDebug :: OptType
oDebug = Option "d" ["debug"]
(NoArg (\ opts -> Ok opts { optDebug = True }))
"Enable debug messages"
oNoUserChecks :: OptType
oNoUserChecks = Option "" ["no-user-checks"]
(NoArg (\ opts -> Ok opts { optNoUserChecks = True }))
"Ignore user checks"
oPort :: Int -> OptType
oPort def = Option "p" ["--port"]
(reqWithConversion (tryRead "reading port")
(\port opts -> Ok opts { optPort = Just port }) "PORT")
("Network port (default: " ++ show def ++ ")")
-- | Usage info.
usageHelp :: String -> [OptType] -> String
usageHelp progname =
usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
progname Version.version progname)
-- | Command line parser, using the 'Options' structure.
parseOpts :: [String] -- ^ The command line arguments
-> String -- ^ The program name
-> [OptType] -- ^ The supported command line options
-> IO (DaemonOptions, [String]) -- ^ The resulting options
-- and leftover arguments
parseOpts argv progname options =
case getOpt Permute options argv of
(opt_list, args, []) ->
do
parsed_opts <-
case foldM (flip id) defaultOptions opt_list of
Bad msg -> do
hPutStrLn stderr "Error while parsing command\
\line arguments:"
hPutStrLn stderr msg
exitWith $ ExitFailure 1
Ok val -> return val
return (parsed_opts, args)
(_, _, errs) -> do
hPutStrLn stderr $ "Command line error: " ++ concat errs
hPutStrLn stderr $ usageHelp progname options
exitWith $ ExitFailure 2
-- | Small wrapper over getArgs and 'parseOpts'.
parseArgs :: String -> [OptType] -> IO (DaemonOptions, [String])
parseArgs cmd options = do
cmd_args <- getArgs
parseOpts cmd_args cmd options
-- * Daemon-related functions
-- | PID file mode.
pidFileMode :: FileMode
pidFileMode = unionFileModes ownerReadMode ownerWriteMode
-- | Writes a PID file and locks it.
_writePidFile :: FilePath -> IO Fd
_writePidFile path = do
fd <- createFile path pidFileMode
setLock fd (WriteLock, AbsoluteSeek, 0, 0)
my_pid <- getProcessID
_ <- fdWrite fd (show my_pid ++ "\n")
return fd
-- | Wrapper over '_writePidFile' that transforms IO exceptions into a
-- 'Bad' value.
writePidFile :: FilePath -> IO (Result Fd)
writePidFile path = do
catch (fmap Ok $ _writePidFile path) (return . Bad . show)
-- | Sets up a daemon's environment.
setupDaemonEnv :: FilePath -> FileMode -> IO ()
setupDaemonEnv cwd umask = do
changeWorkingDirectory cwd
_ <- setFileCreationMask umask
_ <- createSession
return ()
-- | Run an I/O action as a daemon.
--
-- WARNING: this only works in single-threaded mode (either using the
-- single-threaded runtime, or using the multi-threaded one but with
-- only one OS thread, i.e. -N1).
--
-- FIXME: this doesn't support error reporting and the prepfn
-- functionality.
daemonize :: IO () -> IO ()
daemonize action = do
-- first fork
_ <- forkProcess $ do
-- in the child
setupDaemonEnv "/" (unionFileModes groupModes otherModes)
_ <- forkProcess action
exitImmediately ExitSuccess
exitImmediately ExitSuccess
-- | Generic daemon startup.
genericMain :: GanetiDaemon -> [OptType] -> (DaemonOptions -> IO ()) -> IO ()
genericMain daemon options main = do
let progname = daemonName daemon
(opts, args) <- parseArgs progname options
when (optShowHelp opts) $ do
putStr $ usageHelp progname options
exitWith ExitSuccess
when (optShowVer opts) $ do
printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
progname Version.version
compilerName (Data.Version.showVersion compilerVersion)
os arch :: IO ()
exitWith ExitSuccess
unless (null args) $ do
hPutStrLn stderr "This program doesn't take any arguments"
exitWith $ ExitFailure C.exitFailure
unless (optNoUserChecks opts) $ do
runtimeEnts <- getEnts
case runtimeEnts of
Bad msg -> do
hPutStrLn stderr $ "Can't find required user/groups: " ++ msg
exitWith $ ExitFailure C.exitFailure
Ok ents -> verifyDaemonUser daemon ents
let processFn = if optDaemonize opts then daemonize else id
processFn $ innerMain daemon opts (main opts)
-- | Inner daemon function.
--
-- This is executed after daemonization.
innerMain :: GanetiDaemon -> DaemonOptions -> IO () -> IO ()
innerMain daemon opts main = do
setupLogging (daemonLogFile daemon) (daemonName daemon) (optDebug opts)
(not (optDaemonize opts)) False
pid_fd <- writePidFile (daemonPidFile daemon)
case pid_fd of
Bad msg -> do
hPutStrLn stderr $ "Cannot write PID file; already locked? Error: " ++
msg
exitWith $ ExitFailure 1
_ -> return ()
logNotice "starting"
main
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment