Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / Daemon.hs @ 5cefb2b2

History | View | Annotate | Download (11 kB)

1 6ec7a50e Iustin Pop
{-| Implementation of the generic daemon functionality.
2 6ec7a50e Iustin Pop
3 6ec7a50e Iustin Pop
-}
4 6ec7a50e Iustin Pop
5 6ec7a50e Iustin Pop
{-
6 6ec7a50e Iustin Pop
7 6ec7a50e Iustin Pop
Copyright (C) 2011, 2012 Google Inc.
8 6ec7a50e Iustin Pop
9 6ec7a50e Iustin Pop
This program is free software; you can redistribute it and/or modify
10 6ec7a50e Iustin Pop
it under the terms of the GNU General Public License as published by
11 6ec7a50e Iustin Pop
the Free Software Foundation; either version 2 of the License, or
12 6ec7a50e Iustin Pop
(at your option) any later version.
13 6ec7a50e Iustin Pop
14 6ec7a50e Iustin Pop
This program is distributed in the hope that it will be useful, but
15 6ec7a50e Iustin Pop
WITHOUT ANY WARRANTY; without even the implied warranty of
16 6ec7a50e Iustin Pop
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 6ec7a50e Iustin Pop
General Public License for more details.
18 6ec7a50e Iustin Pop
19 6ec7a50e Iustin Pop
You should have received a copy of the GNU General Public License
20 6ec7a50e Iustin Pop
along with this program; if not, write to the Free Software
21 6ec7a50e Iustin Pop
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 6ec7a50e Iustin Pop
02110-1301, USA.
23 6ec7a50e Iustin Pop
24 6ec7a50e Iustin Pop
-}
25 6ec7a50e Iustin Pop
26 6ec7a50e Iustin Pop
module Ganeti.Daemon
27 6ec7a50e Iustin Pop
  ( DaemonOptions(..)
28 6ec7a50e Iustin Pop
  , OptType
29 6ec7a50e Iustin Pop
  , defaultOptions
30 6ec7a50e Iustin Pop
  , oShowHelp
31 6ec7a50e Iustin Pop
  , oShowVer
32 6ec7a50e Iustin Pop
  , oNoDaemonize
33 6ec7a50e Iustin Pop
  , oNoUserChecks
34 6ec7a50e Iustin Pop
  , oDebug
35 6ec7a50e Iustin Pop
  , oPort
36 152e05e1 Iustin Pop
  , oBindAddress
37 b714ff89 Iustin Pop
  , oSyslogUsage
38 6ec7a50e Iustin Pop
  , parseArgs
39 152e05e1 Iustin Pop
  , parseAddress
40 6ec7a50e Iustin Pop
  , writePidFile
41 6ec7a50e Iustin Pop
  , genericMain
42 6ec7a50e Iustin Pop
  ) where
43 6ec7a50e Iustin Pop
44 79ac58fa Iustin Pop
import Control.Exception
45 6ec7a50e Iustin Pop
import Control.Monad
46 6ec7a50e Iustin Pop
import qualified Data.Version
47 6ec7a50e Iustin Pop
import Data.Word
48 152e05e1 Iustin Pop
import qualified Network.Socket as Socket
49 79ac58fa Iustin Pop
import Prelude hiding (catch)
50 6ec7a50e Iustin Pop
import System.Console.GetOpt
51 6ec7a50e Iustin Pop
import System.Exit
52 6ec7a50e Iustin Pop
import System.Environment
53 6ec7a50e Iustin Pop
import System.Info
54 6ec7a50e Iustin Pop
import System.IO
55 6ec7a50e Iustin Pop
import System.Posix.Directory
56 6ec7a50e Iustin Pop
import System.Posix.Files
57 6ec7a50e Iustin Pop
import System.Posix.IO
58 6ec7a50e Iustin Pop
import System.Posix.Process
59 6ec7a50e Iustin Pop
import System.Posix.Types
60 6ec7a50e Iustin Pop
import Text.Printf
61 6ec7a50e Iustin Pop
62 6ec7a50e Iustin Pop
import Ganeti.Logging
63 6ec7a50e Iustin Pop
import Ganeti.Runtime
64 6ec7a50e Iustin Pop
import Ganeti.BasicTypes
65 6ec7a50e Iustin Pop
import Ganeti.HTools.Utils
66 6ec7a50e Iustin Pop
import qualified Ganeti.HTools.Version as Version(version)
67 6ec7a50e Iustin Pop
import qualified Ganeti.Constants as C
68 152e05e1 Iustin Pop
import qualified Ganeti.Ssconf as Ssconf
69 6ec7a50e Iustin Pop
70 6ec7a50e Iustin Pop
-- * Data types
71 6ec7a50e Iustin Pop
72 6ec7a50e Iustin Pop
-- | Command line options structure.
73 6ec7a50e Iustin Pop
data DaemonOptions = DaemonOptions
74 6ec7a50e Iustin Pop
  { optShowHelp     :: Bool           -- ^ Just show the help
75 6ec7a50e Iustin Pop
  , optShowVer      :: Bool           -- ^ Just show the program version
76 6ec7a50e Iustin Pop
  , optDaemonize    :: Bool           -- ^ Whether to daemonize or not
77 6ec7a50e Iustin Pop
  , optPort         :: Maybe Word16   -- ^ Override for the network port
78 6ec7a50e Iustin Pop
  , optDebug        :: Bool           -- ^ Enable debug messages
79 6ec7a50e Iustin Pop
  , optNoUserChecks :: Bool           -- ^ Ignore user checks
80 152e05e1 Iustin Pop
  , optBindAddress  :: Maybe String   -- ^ Override for the bind address
81 b714ff89 Iustin Pop
  , optSyslogUsage  :: Maybe SyslogUsage -- ^ Override for Syslog usage
82 6ec7a50e Iustin Pop
  }
83 6ec7a50e Iustin Pop
84 6ec7a50e Iustin Pop
-- | Default values for the command line options.
85 6ec7a50e Iustin Pop
defaultOptions :: DaemonOptions
86 6ec7a50e Iustin Pop
defaultOptions  = DaemonOptions
87 6ec7a50e Iustin Pop
  { optShowHelp     = False
88 6ec7a50e Iustin Pop
  , optShowVer      = False
89 6ec7a50e Iustin Pop
  , optDaemonize    = True
90 6ec7a50e Iustin Pop
  , optPort         = Nothing
91 6ec7a50e Iustin Pop
  , optDebug        = False
92 6ec7a50e Iustin Pop
  , optNoUserChecks = False
93 152e05e1 Iustin Pop
  , optBindAddress  = Nothing
94 b714ff89 Iustin Pop
  , optSyslogUsage  = Nothing
95 6ec7a50e Iustin Pop
  }
96 6ec7a50e Iustin Pop
97 6ec7a50e Iustin Pop
-- | Abrreviation for the option type.
98 6ec7a50e Iustin Pop
type OptType = OptDescr (DaemonOptions -> Result DaemonOptions)
99 6ec7a50e Iustin Pop
100 6ec7a50e Iustin Pop
-- | Helper function for required arguments which need to be converted
101 6ec7a50e Iustin Pop
-- as opposed to stored just as string.
102 6ec7a50e Iustin Pop
reqWithConversion :: (String -> Result a)
103 6ec7a50e Iustin Pop
                  -> (a -> DaemonOptions -> Result DaemonOptions)
104 6ec7a50e Iustin Pop
                  -> String
105 6ec7a50e Iustin Pop
                  -> ArgDescr (DaemonOptions -> Result DaemonOptions)
106 6ec7a50e Iustin Pop
reqWithConversion conversion_fn updater_fn metavar =
107 6ec7a50e Iustin Pop
  ReqArg (\string_opt opts -> do
108 6ec7a50e Iustin Pop
            parsed_value <- conversion_fn string_opt
109 6ec7a50e Iustin Pop
            updater_fn parsed_value opts) metavar
110 6ec7a50e Iustin Pop
111 6ec7a50e Iustin Pop
-- * Command line options
112 6ec7a50e Iustin Pop
113 6ec7a50e Iustin Pop
oShowHelp :: OptType
114 6ec7a50e Iustin Pop
oShowHelp = Option "h" ["help"]
115 6ec7a50e Iustin Pop
            (NoArg (\ opts -> Ok opts { optShowHelp = True}))
116 6ec7a50e Iustin Pop
            "Show the help message and exit"
117 6ec7a50e Iustin Pop
118 6ec7a50e Iustin Pop
oShowVer :: OptType
119 6ec7a50e Iustin Pop
oShowVer = Option "V" ["version"]
120 6ec7a50e Iustin Pop
           (NoArg (\ opts -> Ok opts { optShowVer = True}))
121 6ec7a50e Iustin Pop
           "Show the version of the program and exit"
122 6ec7a50e Iustin Pop
123 6ec7a50e Iustin Pop
oNoDaemonize :: OptType
124 6ec7a50e Iustin Pop
oNoDaemonize = Option "f" ["foreground"]
125 6ec7a50e Iustin Pop
               (NoArg (\ opts -> Ok opts { optDaemonize = False}))
126 6ec7a50e Iustin Pop
               "Don't detach from the current terminal"
127 6ec7a50e Iustin Pop
128 6ec7a50e Iustin Pop
oDebug :: OptType
129 6ec7a50e Iustin Pop
oDebug = Option "d" ["debug"]
130 6ec7a50e Iustin Pop
         (NoArg (\ opts -> Ok opts { optDebug = True }))
131 6ec7a50e Iustin Pop
         "Enable debug messages"
132 6ec7a50e Iustin Pop
133 6ec7a50e Iustin Pop
oNoUserChecks :: OptType
134 6ec7a50e Iustin Pop
oNoUserChecks = Option "" ["no-user-checks"]
135 6ec7a50e Iustin Pop
         (NoArg (\ opts -> Ok opts { optNoUserChecks = True }))
136 6ec7a50e Iustin Pop
         "Ignore user checks"
137 6ec7a50e Iustin Pop
138 6ec7a50e Iustin Pop
oPort :: Int -> OptType
139 e6812a1a Iustin Pop
oPort def = Option "p" ["port"]
140 6ec7a50e Iustin Pop
            (reqWithConversion (tryRead "reading port")
141 6ec7a50e Iustin Pop
             (\port opts -> Ok opts { optPort = Just port }) "PORT")
142 6ec7a50e Iustin Pop
            ("Network port (default: " ++ show def ++ ")")
143 6ec7a50e Iustin Pop
144 152e05e1 Iustin Pop
oBindAddress :: OptType
145 152e05e1 Iustin Pop
oBindAddress = Option "b" ["bind"]
146 152e05e1 Iustin Pop
               (ReqArg (\addr opts -> Ok opts { optBindAddress = Just addr })
147 152e05e1 Iustin Pop
                "ADDR")
148 152e05e1 Iustin Pop
               "Bind address (default depends on cluster configuration)"
149 152e05e1 Iustin Pop
150 b714ff89 Iustin Pop
oSyslogUsage :: OptType
151 b714ff89 Iustin Pop
oSyslogUsage = Option "" ["syslog"]
152 b714ff89 Iustin Pop
               (reqWithConversion syslogUsageFromRaw
153 b714ff89 Iustin Pop
                (\su opts -> Ok opts { optSyslogUsage = Just su })
154 b714ff89 Iustin Pop
                "SYSLOG")
155 b714ff89 Iustin Pop
               ("Enable logging to syslog (except debug \
156 b714ff89 Iustin Pop
                \messages); one of 'no', 'yes' or 'only' [" ++ C.syslogUsage ++
157 b714ff89 Iustin Pop
                "]")
158 b714ff89 Iustin Pop
159 6ec7a50e Iustin Pop
-- | Usage info.
160 6ec7a50e Iustin Pop
usageHelp :: String -> [OptType] -> String
161 6ec7a50e Iustin Pop
usageHelp progname =
162 6ec7a50e Iustin Pop
  usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
163 6ec7a50e Iustin Pop
             progname Version.version progname)
164 6ec7a50e Iustin Pop
165 6ec7a50e Iustin Pop
-- | Command line parser, using the 'Options' structure.
166 6ec7a50e Iustin Pop
parseOpts :: [String]               -- ^ The command line arguments
167 6ec7a50e Iustin Pop
          -> String                 -- ^ The program name
168 6ec7a50e Iustin Pop
          -> [OptType]              -- ^ The supported command line options
169 6ec7a50e Iustin Pop
          -> IO (DaemonOptions, [String]) -- ^ The resulting options
170 6ec7a50e Iustin Pop
                                          -- and leftover arguments
171 6ec7a50e Iustin Pop
parseOpts argv progname options =
172 6ec7a50e Iustin Pop
  case getOpt Permute options argv of
173 6ec7a50e Iustin Pop
    (opt_list, args, []) ->
174 6ec7a50e Iustin Pop
      do
175 6ec7a50e Iustin Pop
        parsed_opts <-
176 88a10df5 Iustin Pop
          exitIfBad "Error while parsing command line arguments" $
177 88a10df5 Iustin Pop
          foldM (flip id) defaultOptions opt_list
178 6ec7a50e Iustin Pop
        return (parsed_opts, args)
179 6ec7a50e Iustin Pop
    (_, _, errs) -> do
180 6ec7a50e Iustin Pop
      hPutStrLn stderr $ "Command line error: "  ++ concat errs
181 6ec7a50e Iustin Pop
      hPutStrLn stderr $ usageHelp progname options
182 6ec7a50e Iustin Pop
      exitWith $ ExitFailure 2
183 6ec7a50e Iustin Pop
184 6ec7a50e Iustin Pop
-- | Small wrapper over getArgs and 'parseOpts'.
185 6ec7a50e Iustin Pop
parseArgs :: String -> [OptType] -> IO (DaemonOptions, [String])
186 6ec7a50e Iustin Pop
parseArgs cmd options = do
187 6ec7a50e Iustin Pop
  cmd_args <- getArgs
188 6ec7a50e Iustin Pop
  parseOpts cmd_args cmd options
189 6ec7a50e Iustin Pop
190 6ec7a50e Iustin Pop
-- * Daemon-related functions
191 6ec7a50e Iustin Pop
-- | PID file mode.
192 6ec7a50e Iustin Pop
pidFileMode :: FileMode
193 6ec7a50e Iustin Pop
pidFileMode = unionFileModes ownerReadMode ownerWriteMode
194 6ec7a50e Iustin Pop
195 6ec7a50e Iustin Pop
-- | Writes a PID file and locks it.
196 6ec7a50e Iustin Pop
_writePidFile :: FilePath -> IO Fd
197 6ec7a50e Iustin Pop
_writePidFile path = do
198 6ec7a50e Iustin Pop
  fd <- createFile path pidFileMode
199 6ec7a50e Iustin Pop
  setLock fd (WriteLock, AbsoluteSeek, 0, 0)
200 6ec7a50e Iustin Pop
  my_pid <- getProcessID
201 6ec7a50e Iustin Pop
  _ <- fdWrite fd (show my_pid ++ "\n")
202 6ec7a50e Iustin Pop
  return fd
203 6ec7a50e Iustin Pop
204 79ac58fa Iustin Pop
-- | Helper to format an IOError.
205 79ac58fa Iustin Pop
formatIOError :: String -> IOError -> String
206 79ac58fa Iustin Pop
formatIOError msg err = msg ++ ": " ++  show err
207 79ac58fa Iustin Pop
208 6ec7a50e Iustin Pop
-- | Wrapper over '_writePidFile' that transforms IO exceptions into a
209 6ec7a50e Iustin Pop
-- 'Bad' value.
210 6ec7a50e Iustin Pop
writePidFile :: FilePath -> IO (Result Fd)
211 6ec7a50e Iustin Pop
writePidFile path = do
212 79ac58fa Iustin Pop
  catch (fmap Ok $ _writePidFile path)
213 79ac58fa Iustin Pop
    (return . Bad . formatIOError "Failure during writing of the pid file")
214 6ec7a50e Iustin Pop
215 6ec7a50e Iustin Pop
-- | Sets up a daemon's environment.
216 6ec7a50e Iustin Pop
setupDaemonEnv :: FilePath -> FileMode -> IO ()
217 6ec7a50e Iustin Pop
setupDaemonEnv cwd umask = do
218 6ec7a50e Iustin Pop
  changeWorkingDirectory cwd
219 6ec7a50e Iustin Pop
  _ <- setFileCreationMask umask
220 6ec7a50e Iustin Pop
  _ <- createSession
221 6ec7a50e Iustin Pop
  return ()
222 6ec7a50e Iustin Pop
223 152e05e1 Iustin Pop
-- | Computes the default bind address for a given family.
224 152e05e1 Iustin Pop
defaultBindAddr :: Int                  -- ^ The port we want
225 152e05e1 Iustin Pop
                -> Socket.Family        -- ^ The cluster IP family
226 152e05e1 Iustin Pop
                -> Result (Socket.Family, Socket.SockAddr)
227 152e05e1 Iustin Pop
defaultBindAddr port Socket.AF_INET =
228 152e05e1 Iustin Pop
  Ok $ (Socket.AF_INET,
229 152e05e1 Iustin Pop
        Socket.SockAddrInet (fromIntegral port) Socket.iNADDR_ANY)
230 152e05e1 Iustin Pop
defaultBindAddr port Socket.AF_INET6 =
231 152e05e1 Iustin Pop
  Ok $ (Socket.AF_INET6,
232 152e05e1 Iustin Pop
        Socket.SockAddrInet6 (fromIntegral port) 0 Socket.iN6ADDR_ANY 0)
233 152e05e1 Iustin Pop
defaultBindAddr _ fam = Bad $ "Unsupported address family: " ++ show fam
234 152e05e1 Iustin Pop
235 152e05e1 Iustin Pop
-- | Default hints for the resolver
236 152e05e1 Iustin Pop
resolveAddrHints :: Maybe Socket.AddrInfo
237 152e05e1 Iustin Pop
resolveAddrHints =
238 152e05e1 Iustin Pop
  Just Socket.defaultHints { Socket.addrFlags = [Socket.AI_NUMERICHOST,
239 152e05e1 Iustin Pop
                                                 Socket.AI_NUMERICSERV] }
240 152e05e1 Iustin Pop
241 152e05e1 Iustin Pop
-- | Resolves a numeric address.
242 152e05e1 Iustin Pop
resolveAddr :: Int -> String -> IO (Result (Socket.Family, Socket.SockAddr))
243 152e05e1 Iustin Pop
resolveAddr port str = do
244 152e05e1 Iustin Pop
  resolved <- Socket.getAddrInfo resolveAddrHints (Just str) (Just (show port))
245 152e05e1 Iustin Pop
  return $ case resolved of
246 152e05e1 Iustin Pop
             [] -> Bad "Invalid results from lookup?"
247 152e05e1 Iustin Pop
             best:_ -> Ok $ (Socket.addrFamily best, Socket.addrAddress best)
248 152e05e1 Iustin Pop
249 152e05e1 Iustin Pop
-- | Based on the options, compute the socket address to use for the
250 152e05e1 Iustin Pop
-- daemon.
251 152e05e1 Iustin Pop
parseAddress :: DaemonOptions      -- ^ Command line options
252 152e05e1 Iustin Pop
             -> Int                -- ^ Default port for this daemon
253 152e05e1 Iustin Pop
             -> IO (Result (Socket.Family, Socket.SockAddr))
254 152e05e1 Iustin Pop
parseAddress opts defport = do
255 152e05e1 Iustin Pop
  let port = maybe defport fromIntegral $ optPort opts
256 152e05e1 Iustin Pop
  def_family <- Ssconf.getPrimaryIPFamily Nothing
257 152e05e1 Iustin Pop
  ainfo <- case optBindAddress opts of
258 152e05e1 Iustin Pop
             Nothing -> return (def_family >>= defaultBindAddr port)
259 152e05e1 Iustin Pop
             Just saddr -> catch (resolveAddr port saddr)
260 152e05e1 Iustin Pop
                           (annotateIOError $ "Invalid address " ++ saddr)
261 152e05e1 Iustin Pop
  return ainfo
262 152e05e1 Iustin Pop
263 6ec7a50e Iustin Pop
-- | Run an I/O action as a daemon.
264 6ec7a50e Iustin Pop
--
265 6ec7a50e Iustin Pop
-- WARNING: this only works in single-threaded mode (either using the
266 6ec7a50e Iustin Pop
-- single-threaded runtime, or using the multi-threaded one but with
267 6ec7a50e Iustin Pop
-- only one OS thread, i.e. -N1).
268 6ec7a50e Iustin Pop
--
269 6ec7a50e Iustin Pop
-- FIXME: this doesn't support error reporting and the prepfn
270 6ec7a50e Iustin Pop
-- functionality.
271 6ec7a50e Iustin Pop
daemonize :: IO () -> IO ()
272 6ec7a50e Iustin Pop
daemonize action = do
273 6ec7a50e Iustin Pop
  -- first fork
274 6ec7a50e Iustin Pop
  _ <- forkProcess $ do
275 6ec7a50e Iustin Pop
    -- in the child
276 6ec7a50e Iustin Pop
    setupDaemonEnv "/" (unionFileModes groupModes otherModes)
277 6ec7a50e Iustin Pop
    _ <- forkProcess action
278 6ec7a50e Iustin Pop
    exitImmediately ExitSuccess
279 6ec7a50e Iustin Pop
  exitImmediately ExitSuccess
280 6ec7a50e Iustin Pop
281 6ec7a50e Iustin Pop
-- | Generic daemon startup.
282 6ec7a50e Iustin Pop
genericMain :: GanetiDaemon -> [OptType] -> (DaemonOptions -> IO ()) -> IO ()
283 6ec7a50e Iustin Pop
genericMain daemon options main = do
284 6ec7a50e Iustin Pop
  let progname = daemonName daemon
285 6ec7a50e Iustin Pop
  (opts, args) <- parseArgs progname options
286 6ec7a50e Iustin Pop
287 6ec7a50e Iustin Pop
  when (optShowHelp opts) $ do
288 6ec7a50e Iustin Pop
    putStr $ usageHelp progname options
289 6ec7a50e Iustin Pop
    exitWith ExitSuccess
290 6ec7a50e Iustin Pop
  when (optShowVer opts) $ do
291 6ec7a50e Iustin Pop
    printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
292 6ec7a50e Iustin Pop
           progname Version.version
293 6ec7a50e Iustin Pop
           compilerName (Data.Version.showVersion compilerVersion)
294 6ec7a50e Iustin Pop
           os arch :: IO ()
295 6ec7a50e Iustin Pop
    exitWith ExitSuccess
296 88a10df5 Iustin Pop
297 88a10df5 Iustin Pop
  exitUnless (null args) "This program doesn't take any arguments"
298 6ec7a50e Iustin Pop
299 6ec7a50e Iustin Pop
  unless (optNoUserChecks opts) $ do
300 6ec7a50e Iustin Pop
    runtimeEnts <- getEnts
301 88a10df5 Iustin Pop
    ents <- exitIfBad "Can't find required user/groups" runtimeEnts
302 88a10df5 Iustin Pop
    verifyDaemonUser daemon ents
303 6ec7a50e Iustin Pop
304 b714ff89 Iustin Pop
  syslog <- case optSyslogUsage opts of
305 88a10df5 Iustin Pop
              Nothing -> exitIfBad "Invalid cluster syslog setting" $
306 b714ff89 Iustin Pop
                         syslogUsageFromRaw C.syslogUsage
307 b714ff89 Iustin Pop
              Just v -> return v
308 6ec7a50e Iustin Pop
  let processFn = if optDaemonize opts then daemonize else id
309 b714ff89 Iustin Pop
  processFn $ innerMain daemon opts syslog (main opts)
310 6ec7a50e Iustin Pop
311 6ec7a50e Iustin Pop
-- | Inner daemon function.
312 6ec7a50e Iustin Pop
--
313 6ec7a50e Iustin Pop
-- This is executed after daemonization.
314 b714ff89 Iustin Pop
innerMain :: GanetiDaemon -> DaemonOptions -> SyslogUsage -> IO () -> IO ()
315 b714ff89 Iustin Pop
innerMain daemon opts syslog main = do
316 6ec7a50e Iustin Pop
  setupLogging (daemonLogFile daemon) (daemonName daemon) (optDebug opts)
317 b714ff89 Iustin Pop
                 (not (optDaemonize opts)) False syslog
318 6ec7a50e Iustin Pop
  pid_fd <- writePidFile (daemonPidFile daemon)
319 88a10df5 Iustin Pop
  _ <- exitIfBad "Cannot write PID file; already locked? Error" pid_fd
320 6ec7a50e Iustin Pop
  logNotice "starting"
321 6ec7a50e Iustin Pop
  main