1 {-| Implementation of the generic daemon functionality.
7 Copyright (C) 2011, 2012 Google Inc.
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
42 import qualified Data.Version
44 import System.Console.GetOpt
46 import System.Environment
49 import System.Posix.Directory
50 import System.Posix.Files
51 import System.Posix.IO
52 import System.Posix.Process
53 import System.Posix.Types
58 import Ganeti.BasicTypes
59 import Ganeti.HTools.Utils
60 import qualified Ganeti.HTools.Version as Version(version)
61 import qualified Ganeti.Constants as C
65 -- | Command line options structure.
66 data DaemonOptions = DaemonOptions
67 { optShowHelp :: Bool -- ^ Just show the help
68 , optShowVer :: Bool -- ^ Just show the program version
69 , optDaemonize :: Bool -- ^ Whether to daemonize or not
70 , optPort :: Maybe Word16 -- ^ Override for the network port
71 , optDebug :: Bool -- ^ Enable debug messages
72 , optNoUserChecks :: Bool -- ^ Ignore user checks
75 -- | Default values for the command line options.
76 defaultOptions :: DaemonOptions
77 defaultOptions = DaemonOptions
83 , optNoUserChecks = False
86 -- | Abrreviation for the option type.
87 type OptType = OptDescr (DaemonOptions -> Result DaemonOptions)
89 -- | Helper function for required arguments which need to be converted
90 -- as opposed to stored just as string.
91 reqWithConversion :: (String -> Result a)
92 -> (a -> DaemonOptions -> Result DaemonOptions)
94 -> ArgDescr (DaemonOptions -> Result DaemonOptions)
95 reqWithConversion conversion_fn updater_fn metavar =
96 ReqArg (\string_opt opts -> do
97 parsed_value <- conversion_fn string_opt
98 updater_fn parsed_value opts) metavar
100 -- * Command line options
103 oShowHelp = Option "h" ["help"]
104 (NoArg (\ opts -> Ok opts { optShowHelp = True}))
105 "Show the help message and exit"
108 oShowVer = Option "V" ["version"]
109 (NoArg (\ opts -> Ok opts { optShowVer = True}))
110 "Show the version of the program and exit"
112 oNoDaemonize :: OptType
113 oNoDaemonize = Option "f" ["foreground"]
114 (NoArg (\ opts -> Ok opts { optDaemonize = False}))
115 "Don't detach from the current terminal"
118 oDebug = Option "d" ["debug"]
119 (NoArg (\ opts -> Ok opts { optDebug = True }))
120 "Enable debug messages"
122 oNoUserChecks :: OptType
123 oNoUserChecks = Option "" ["no-user-checks"]
124 (NoArg (\ opts -> Ok opts { optNoUserChecks = True }))
127 oPort :: Int -> OptType
128 oPort def = Option "p" ["port"]
129 (reqWithConversion (tryRead "reading port")
130 (\port opts -> Ok opts { optPort = Just port }) "PORT")
131 ("Network port (default: " ++ show def ++ ")")
134 usageHelp :: String -> [OptType] -> String
136 usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
137 progname Version.version progname)
139 -- | Command line parser, using the 'Options' structure.
140 parseOpts :: [String] -- ^ The command line arguments
141 -> String -- ^ The program name
142 -> [OptType] -- ^ The supported command line options
143 -> IO (DaemonOptions, [String]) -- ^ The resulting options
144 -- and leftover arguments
145 parseOpts argv progname options =
146 case getOpt Permute options argv of
147 (opt_list, args, []) ->
150 case foldM (flip id) defaultOptions opt_list of
152 hPutStrLn stderr "Error while parsing command\
155 exitWith $ ExitFailure 1
157 return (parsed_opts, args)
159 hPutStrLn stderr $ "Command line error: " ++ concat errs
160 hPutStrLn stderr $ usageHelp progname options
161 exitWith $ ExitFailure 2
163 -- | Small wrapper over getArgs and 'parseOpts'.
164 parseArgs :: String -> [OptType] -> IO (DaemonOptions, [String])
165 parseArgs cmd options = do
167 parseOpts cmd_args cmd options
169 -- * Daemon-related functions
171 pidFileMode :: FileMode
172 pidFileMode = unionFileModes ownerReadMode ownerWriteMode
174 -- | Writes a PID file and locks it.
175 _writePidFile :: FilePath -> IO Fd
176 _writePidFile path = do
177 fd <- createFile path pidFileMode
178 setLock fd (WriteLock, AbsoluteSeek, 0, 0)
179 my_pid <- getProcessID
180 _ <- fdWrite fd (show my_pid ++ "\n")
183 -- | Wrapper over '_writePidFile' that transforms IO exceptions into a
185 writePidFile :: FilePath -> IO (Result Fd)
186 writePidFile path = do
187 catch (fmap Ok $ _writePidFile path) (return . Bad . show)
189 -- | Sets up a daemon's environment.
190 setupDaemonEnv :: FilePath -> FileMode -> IO ()
191 setupDaemonEnv cwd umask = do
192 changeWorkingDirectory cwd
193 _ <- setFileCreationMask umask
197 -- | Run an I/O action as a daemon.
199 -- WARNING: this only works in single-threaded mode (either using the
200 -- single-threaded runtime, or using the multi-threaded one but with
201 -- only one OS thread, i.e. -N1).
203 -- FIXME: this doesn't support error reporting and the prepfn
205 daemonize :: IO () -> IO ()
206 daemonize action = do
208 _ <- forkProcess $ do
210 setupDaemonEnv "/" (unionFileModes groupModes otherModes)
211 _ <- forkProcess action
212 exitImmediately ExitSuccess
213 exitImmediately ExitSuccess
215 -- | Generic daemon startup.
216 genericMain :: GanetiDaemon -> [OptType] -> (DaemonOptions -> IO ()) -> IO ()
217 genericMain daemon options main = do
218 let progname = daemonName daemon
219 (opts, args) <- parseArgs progname options
221 when (optShowHelp opts) $ do
222 putStr $ usageHelp progname options
224 when (optShowVer opts) $ do
225 printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
226 progname Version.version
227 compilerName (Data.Version.showVersion compilerVersion)
230 unless (null args) $ do
231 hPutStrLn stderr "This program doesn't take any arguments"
232 exitWith $ ExitFailure C.exitFailure
234 unless (optNoUserChecks opts) $ do
235 runtimeEnts <- getEnts
238 hPutStrLn stderr $ "Can't find required user/groups: " ++ msg
239 exitWith $ ExitFailure C.exitFailure
240 Ok ents -> verifyDaemonUser daemon ents
242 let processFn = if optDaemonize opts then daemonize else id
243 processFn $ innerMain daemon opts (main opts)
245 -- | Inner daemon function.
247 -- This is executed after daemonization.
248 innerMain :: GanetiDaemon -> DaemonOptions -> IO () -> IO ()
249 innerMain daemon opts main = do
250 setupLogging (daemonLogFile daemon) (daemonName daemon) (optDebug opts)
251 (not (optDaemonize opts)) False
252 pid_fd <- writePidFile (daemonPidFile daemon)
255 hPutStrLn stderr $ "Cannot write PID file; already locked? Error: " ++
257 exitWith $ ExitFailure 1