diff --git a/Makefile.am b/Makefile.am
index aea7174c703de87ed45a7ec08ad8ce5df85617b1..7a377572841ea9c4fc10e7b3f138a8cd9288331d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -412,6 +412,7 @@ HS_LIB_SRCS = \
 	htools/Ganeti/HTools/Program/Hscan.hs \
 	htools/Ganeti/HTools/Program/Hspace.hs \
 	htools/Ganeti/BasicTypes.hs \
+	htools/Ganeti/Common.hs \
 	htools/Ganeti/Compat.hs \
 	htools/Ganeti/Confd.hs \
 	htools/Ganeti/Confd/Server.hs \
@@ -439,6 +440,8 @@ HS_LIB_SRCS = \
 
 HS_TEST_SRCS = \
 	htest/Test/Ganeti/BasicTypes.hs \
+	htest/Test/Ganeti/Common.hs \
+	htest/Test/Ganeti/Daemon.hs \
 	htest/Test/Ganeti/Confd/Utils.hs \
 	htest/Test/Ganeti/HTools/CLI.hs \
 	htest/Test/Ganeti/HTools/Cluster.hs \
diff --git a/htest/Test/Ganeti/Common.hs b/htest/Test/Ganeti/Common.hs
new file mode 100644
index 0000000000000000000000000000000000000000..6c2e3f29a10702029a642afe4505655c3c3de625
--- /dev/null
+++ b/htest/Test/Ganeti/Common.hs
@@ -0,0 +1,127 @@
+{-# LANGUAGE TemplateHaskell #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+{-| Unittests for the 'Ganeti.Common' module.
+
+-}
+
+{-
+
+Copyright (C) 2009, 2010, 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 Test.Ganeti.Common
+  ( testCommon
+  , checkOpt
+  , passFailOpt
+  , checkEarlyExit
+  ) where
+
+import Test.QuickCheck hiding (Result)
+import Test.HUnit
+
+import qualified System.Console.GetOpt as GetOpt
+import System.Exit
+
+import Test.Ganeti.TestHelper
+import Test.Ganeti.TestCommon
+
+import Ganeti.BasicTypes
+import Ganeti.Common
+
+-- | Helper to check for correct parsing of an option.
+checkOpt :: (StandardOptions b) =>
+            (a -> Maybe String) -- ^ Converts the value into a cmdline form
+         -> b                   -- ^ The default options
+         -> (String -> c)       -- ^ Fail test function
+         -> (String -> d -> d -> c) -- ^ Check for equality function
+         -> (a -> d)            -- ^ Transforms the value to a compare val
+         -> (a, GenericOptType b, b -> d) -- ^ Triple of value, the
+                                          -- option, function to
+                                          -- extract the set value
+                                          -- from the options
+         -> c
+checkOpt repr defaults failfn eqcheck valfn
+         (val, opt@(GetOpt.Option _ longs _ _), fn) =
+  case longs of
+    [] -> failfn "no long options?"
+    cmdarg:_ ->
+      case parseOptsInner defaults
+             ["--" ++ cmdarg ++ maybe "" ("=" ++) (repr val)]
+             "prog" [opt] of
+        Left e -> failfn $ "Failed to parse option '" ++ cmdarg ++ ": " ++
+                  show e
+        Right (options, _) -> eqcheck ("Wrong value in option " ++
+                                       cmdarg ++ "?") (valfn val) (fn options)
+
+-- | Helper to check for correct and incorrect parsing of an option.
+passFailOpt :: (StandardOptions b) =>
+               b                 -- ^ The default options
+            -> (String -> c)     -- ^ Fail test function
+            -> c                 -- ^ Pass function
+            -> (GenericOptType b, String, String)
+            -- ^ The list of enabled options, fail value and pass value
+            -> c
+passFailOpt defaults failfn passfn
+              (opt@(GetOpt.Option _ longs _ _), bad, good) =
+  let prefix = "--" ++ head longs ++ "="
+      good_cmd = prefix ++ good
+      bad_cmd = prefix ++ bad in
+  case (parseOptsInner defaults [bad_cmd]  "prog" [opt],
+        parseOptsInner defaults [good_cmd] "prog" [opt]) of
+    (Left _,  Right _) -> passfn
+    (Right _, Right _) -> failfn $ "Command line '" ++ bad_cmd ++
+                          "' succeeded when it shouldn't"
+    (Left  _, Left  _) -> failfn $ "Command line '" ++ good_cmd ++
+                          "' failed when it shouldn't"
+    (Right _, Left  _) ->
+      failfn $ "Command line '" ++ bad_cmd ++
+               "' succeeded when it shouldn't, while command line '" ++
+               good_cmd ++ "' failed when it shouldn't"
+
+-- | Helper to test that a given option is accepted OK with quick exit.
+checkEarlyExit :: (StandardOptions a) =>
+                  a -> String -> [GenericOptType a] -> Assertion
+checkEarlyExit defaults name options =
+  mapM_ (\param ->
+           case parseOptsInner defaults [param] name options of
+             Left (code, _) ->
+               assertEqual ("Program " ++ name ++
+                            " returns invalid code " ++ show code ++
+                            " for option " ++ param) ExitSuccess code
+             _ -> assertFailure $ "Program " ++ name ++
+                  " doesn't consider option " ++
+                  param ++ " as early exit one"
+        ) ["-h", "--help", "-V", "--version"]
+
+-- | Test parseYesNo.
+prop_parse_yes_no :: Bool -> Bool -> String -> Property
+prop_parse_yes_no def testval val =
+  forAll (elements [val, "yes", "no"]) $ \actual_val ->
+  if testval
+    then parseYesNo def Nothing ==? Ok def
+    else let result = parseYesNo def (Just actual_val)
+         in if actual_val `elem` ["yes", "no"]
+              then result ==? Ok (actual_val == "yes")
+              else property $ isBad result
+
+
+testSuite "Common"
+          [ 'prop_parse_yes_no
+          ]
diff --git a/htest/Test/Ganeti/Daemon.hs b/htest/Test/Ganeti/Daemon.hs
new file mode 100644
index 0000000000000000000000000000000000000000..fb6cc757e86d1785ba59c15f095d67d9a94bfd76
--- /dev/null
+++ b/htest/Test/Ganeti/Daemon.hs
@@ -0,0 +1,85 @@
+{-# LANGUAGE TemplateHaskell #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+{-| Unittests for ganeti-htools.
+
+-}
+
+{-
+
+Copyright (C) 2009, 2010, 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 Test.Ganeti.Daemon (testDaemon) where
+
+import Test.QuickCheck hiding (Result)
+import Test.HUnit
+
+import Test.Ganeti.TestHelper
+import Test.Ganeti.TestCommon
+import Test.Ganeti.Common
+
+import Ganeti.Common
+import Ganeti.Daemon as Daemon
+
+-- | Test a few string arguments.
+prop_string_arg :: String -> Property
+prop_string_arg argument =
+  let args = [ (argument, oBindAddress, optBindAddress)
+             ]
+  in conjoin $
+     map (checkOpt Just defaultOptions failTest (const (==?)) Just) args
+
+-- | Test a few integer arguments (only one for now).
+prop_numeric_arg :: Int -> Property
+prop_numeric_arg argument =
+  checkOpt (Just . show) defaultOptions
+    failTest (const (==?)) (Just . fromIntegral)
+    (argument, oPort 0, optPort)
+
+-- | Test a few boolean arguments.
+case_bool_arg :: Assertion
+case_bool_arg =
+  mapM_ (checkOpt (const Nothing) defaultOptions assertFailure
+                  assertEqual id)
+        [ (False, oNoDaemonize,  optDaemonize)
+        , (True,  oDebug,        optDebug)
+        , (True,  oNoUserChecks, optNoUserChecks)
+        ]
+
+-- | Tests a few invalid arguments.
+case_wrong_arg :: Assertion
+case_wrong_arg = do
+  mapM_ (passFailOpt defaultOptions assertFailure (return ()))
+        [ (oSyslogUsage, "foo", "yes")
+        , (oPort 0,      "x",   "10")
+        ]
+
+-- | Test that the option list supports some common options.
+case_stdopts :: Assertion
+case_stdopts =
+  checkEarlyExit defaultOptions "prog" [oShowHelp, oShowVer]
+
+testSuite "Daemon"
+          [ 'prop_string_arg
+          , 'prop_numeric_arg
+          , 'case_bool_arg
+          , 'case_wrong_arg
+          , 'case_stdopts
+          ]
diff --git a/htest/Test/Ganeti/HTools/CLI.hs b/htest/Test/Ganeti/HTools/CLI.hs
index 239dae9acc85a44094a8f5581b9c8ca0de4da59e..b7493d4936845cb4321926a7df53d5648974ded6 100644
--- a/htest/Test/Ganeti/HTools/CLI.hs
+++ b/htest/Test/Ganeti/HTools/CLI.hs
@@ -28,17 +28,18 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 
 module Test.Ganeti.HTools.CLI (testHTools_CLI) where
 
+import Test.HUnit
 import Test.QuickCheck
 
 import Control.Monad
 import Data.List
 import Text.Printf (printf)
-import qualified System.Console.GetOpt as GetOpt
 
 import Test.Ganeti.TestHelper
 import Test.Ganeti.TestCommon
+import Test.Ganeti.Common
 
-import qualified Ganeti.HTools.CLI as CLI
+import Ganeti.HTools.CLI as CLI
 import qualified Ganeti.HTools.Program as Program
 import qualified Ganeti.HTools.Types as Types
 
@@ -46,7 +47,7 @@ import qualified Ganeti.HTools.Types as Types
 prop_parseISpec :: String -> Int -> Int -> Int -> Property
 prop_parseISpec descr dsk mem cpu =
   let str = printf "%d,%d,%d" dsk mem cpu::String
-  in CLI.parseISpecString descr str ==? Types.Ok (Types.RSpec cpu mem dsk)
+  in parseISpecString descr str ==? Types.Ok (Types.RSpec cpu mem dsk)
 
 -- | Test parsing failure due to wrong section count.
 prop_parseISpecFail :: String -> Property
@@ -54,71 +55,76 @@ prop_parseISpecFail descr =
   forAll (choose (0,100) `suchThat` ((/=) 3)) $ \nelems ->
   forAll (replicateM nelems arbitrary) $ \values ->
   let str = intercalate "," $ map show (values::[Int])
-  in case CLI.parseISpecString descr str of
+  in case parseISpecString descr str of
        Types.Ok v -> failTest $ "Expected failure, got " ++ show v
        _ -> passTest
 
--- | Test parseYesNo.
-prop_parseYesNo :: Bool -> Bool -> [Char] -> Property
-prop_parseYesNo def testval val =
-  forAll (elements [val, "yes", "no"]) $ \actual_val ->
-  if testval
-    then CLI.parseYesNo def Nothing ==? Types.Ok def
-    else let result = CLI.parseYesNo def (Just actual_val)
-         in if actual_val `elem` ["yes", "no"]
-              then result ==? Types.Ok (actual_val == "yes")
-              else property $ Types.isBad result
-
--- | Helper to check for correct parsing of string arg.
-checkStringArg :: [Char]
-               -> (GetOpt.OptDescr (CLI.Options -> Types.Result CLI.Options),
-                   CLI.Options -> Maybe [Char])
-               -> Property
-checkStringArg val (opt, fn) =
-  let GetOpt.Option _ longs _ _ = opt
-  in case longs of
-       [] -> failTest "no long options?"
-       cmdarg:_ ->
-         case CLI.parseOptsInner ["--" ++ cmdarg ++ "=" ++ val] "prog" [opt] of
-           Left e -> failTest $ "Failed to parse option: " ++ show e
-           Right (options, _) -> fn options ==? Just val
-
 -- | Test a few string arguments.
-prop_StringArg :: [Char] -> Property
-prop_StringArg argument =
-  let args = [ (CLI.oDataFile,      CLI.optDataFile)
-             , (CLI.oDynuFile,      CLI.optDynuFile)
-             , (CLI.oSaveCluster,   CLI.optSaveCluster)
-             , (CLI.oReplay,        CLI.optReplay)
-             , (CLI.oPrintCommands, CLI.optShowCmds)
-             , (CLI.oLuxiSocket,    CLI.optLuxi)
+prop_string_arg :: String -> Property
+prop_string_arg argument =
+  let args = [ (oDataFile,      optDataFile)
+             , (oDynuFile,      optDynuFile)
+             , (oSaveCluster,   optSaveCluster)
+             , (oPrintCommands, optShowCmds)
+             , (oLuxiSocket,    optLuxi)
+             , (oIAllocSrc,     optIAllocSrc)
+             ]
+  in conjoin $ map (\(o, opt) ->
+                      checkOpt Just defaultOptions
+                      failTest (const (==?)) Just (argument, o, opt)) args
+
+-- | Test a few positive arguments.
+prop_numeric_arg :: Positive Double -> Property
+prop_numeric_arg (Positive argument) =
+  let args = [ (oMaxCpu,     optMcpu)
+             , (oMinDisk,    Just . optMdsk)
+             , (oMinGain,    Just . optMinGain)
+             , (oMinGainLim, Just . optMinGainLim)
+             , (oMinScore,   Just . optMinScore)
              ]
-  in conjoin $ map (checkStringArg argument) args
-
--- | Helper to test that a given option is accepted OK with quick exit.
-checkEarlyExit :: String -> [CLI.OptType] -> String -> Property
-checkEarlyExit name options param =
-  case CLI.parseOptsInner [param] name options of
-    Left (code, _) ->
-      printTestCase ("Program " ++ name ++
-                     " returns invalid code " ++ show code ++
-                     " for option " ++ param) (code == 0)
-    _ -> failTest $ "Program " ++ name ++ " doesn't consider option " ++
-         param ++ " as early exit one"
-
--- | Test that all binaries support some common options. There is
--- nothing actually random about this test...
-prop_stdopts :: Property
-prop_stdopts =
-  let params = ["-h", "--help", "-V", "--version"]
-      opts = map (\(name, (_, o)) -> (name, o)) Program.personalities
-      -- apply checkEarlyExit across the cartesian product of params and opts
-  in conjoin [checkEarlyExit n o p | p <- params, (n, o) <- opts]
+  in conjoin $
+     map (\(x, y) -> checkOpt (Just . show) defaultOptions
+                     failTest (const (==?)) Just (argument, x, y)) args
+
+-- | Test a few boolean arguments.
+case_bool_arg :: Assertion
+case_bool_arg =
+  mapM_ (checkOpt (const Nothing) defaultOptions assertFailure
+                  assertEqual id)
+        [ (False, oDiskMoves,    optDiskMoves)
+        , (False, oInstMoves,    optInstMoves)
+        , (True,  oEvacMode,     optEvacMode)
+        , (True,  oExecJobs,     optExecJobs)
+        , (True,  oNoHeaders,    optNoHeaders)
+        , (True,  oNoSimulation, optNoSimulation)
+        ]
+
+-- | Tests a few invalid arguments.
+case_wrong_arg :: Assertion
+case_wrong_arg = do
+  mapM_ (passFailOpt defaultOptions assertFailure (return ()))
+        [ (oSpindleUse,   "-1", "1")
+        , (oSpindleUse,   "a",  "1")
+        , (oMaxCpu,       "-1", "1")
+        , (oMinDisk,      "a",  "1")
+        , (oMinGainLim,   "a",  "1")
+        , (oMaxSolLength, "x",  "10")
+        , (oStdSpec,      "no-such-spec", "1,1,1")
+        , (oTieredSpec,   "no-such-spec", "1,1,1")
+        ]
+
+-- | Test that all binaries support some common options.
+case_stdopts :: Assertion
+case_stdopts =
+  mapM_ (\(name, (_, o)) -> checkEarlyExit defaultOptions name o)
+      Program.personalities
 
 testSuite "HTools/CLI"
           [ 'prop_parseISpec
           , 'prop_parseISpecFail
-          , 'prop_parseYesNo
-          , 'prop_StringArg
-          , 'prop_stdopts
+          , 'prop_string_arg
+          , 'prop_numeric_arg
+          , 'case_bool_arg
+          , 'case_wrong_arg
+          , 'case_stdopts
           ]
diff --git a/htest/test.hs b/htest/test.hs
index 9940dc91a64630250fd0502edc32cbd9793a3d6c..60ff088975de5ee678441d623801cfbec24ef7a9 100644
--- a/htest/test.hs
+++ b/htest/test.hs
@@ -32,6 +32,8 @@ import System.Environment (getArgs)
 import Test.Ganeti.TestImports ()
 import Test.Ganeti.BasicTypes
 import Test.Ganeti.Confd.Utils
+import Test.Ganeti.Common
+import Test.Ganeti.Daemon
 import Test.Ganeti.HTools.CLI
 import Test.Ganeti.HTools.Cluster
 import Test.Ganeti.HTools.Container
@@ -78,6 +80,8 @@ allTests :: [(Bool, (String, [Test]))]
 allTests =
   [ (True, testBasicTypes)
   , (True, testConfd_Utils)
+  , (True, testCommon)
+  , (True, testDaemon)
   , (True, testHTools_CLI)
   , (True, testHTools_Container)
   , (True, testHTools_Instance)
diff --git a/htools/Ganeti/Common.hs b/htools/Ganeti/Common.hs
new file mode 100644
index 0000000000000000000000000000000000000000..8b10230c08e1d0ab3ed14b6e1184d5d95610c50f
--- /dev/null
+++ b/htools/Ganeti/Common.hs
@@ -0,0 +1,148 @@
+{-| Base common functionality.
+
+This module holds common functionality shared across Ganeti daemons,
+HTools and any other programs.
+
+-}
+
+{-
+
+Copyright (C) 2009, 2010, 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.Common
+  ( GenericOptType
+  , StandardOptions(..)
+  , oShowHelp
+  , oShowVer
+  , usageHelp
+  , versionInfo
+  , reqWithConversion
+  , parseYesNo
+  , parseOpts
+  , parseOptsInner
+  ) where
+
+import Control.Monad (foldM)
+import qualified Data.Version
+import System.Console.GetOpt
+import System.Exit
+import System.Info
+import System.IO
+import Text.Printf (printf)
+
+import Ganeti.BasicTypes
+import qualified Ganeti.Version as Version (version)
+
+-- | Abrreviation for the option type.
+type GenericOptType a = OptDescr (a -> Result a)
+
+-- | Type class for options which support help and version.
+class StandardOptions a where
+  helpRequested :: a -> Bool
+  verRequested  :: a -> Bool
+  requestHelp   :: a -> a
+  requestVer    :: a -> a
+
+-- | Options to request help output.
+oShowHelp :: (StandardOptions a) => GenericOptType a
+oShowHelp = Option "h" ["help"] (NoArg (Ok . requestHelp))
+            "show help"
+
+oShowVer :: (StandardOptions a) => GenericOptType a
+oShowVer = Option "V" ["version"] (NoArg (Ok . requestVer))
+           "show the version of the program"
+
+-- | Usage info.
+usageHelp :: String -> [GenericOptType a] -> String
+usageHelp progname =
+  usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
+             progname Version.version progname)
+
+-- | Show the program version info.
+versionInfo :: String -> String
+versionInfo progname =
+  printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
+         progname Version.version compilerName
+         (Data.Version.showVersion compilerVersion)
+         os arch
+
+-- | Helper for parsing a yes\/no command line flag.
+parseYesNo :: Bool         -- ^ Default value (when we get a @Nothing@)
+           -> Maybe String -- ^ Parameter value
+           -> Result Bool  -- ^ Resulting boolean value
+parseYesNo v Nothing      = return v
+parseYesNo _ (Just "yes") = return True
+parseYesNo _ (Just "no")  = return False
+parseYesNo _ (Just s)     = fail ("Invalid choice '" ++ s ++
+                                  "', pass one of 'yes' or 'no'")
+
+-- | Helper function for required arguments which need to be converted
+-- as opposed to stored just as string.
+reqWithConversion :: (String -> Result a)
+                  -> (a -> b -> Result b)
+                  -> String
+                  -> ArgDescr (b -> Result b)
+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 parser, using a generic 'Options' structure.
+parseOpts :: (StandardOptions a) =>
+             a                      -- ^ The default options
+          -> [String]               -- ^ The command line arguments
+          -> String                 -- ^ The program name
+          -> [GenericOptType a]     -- ^ The supported command line options
+          -> IO (a, [String])       -- ^ The resulting options and
+                                    -- leftover arguments
+parseOpts defaults argv progname options =
+  case parseOptsInner defaults argv progname options of
+    Left (code, msg) -> do
+      hPutStr (if code == ExitSuccess then stdout else stderr) msg
+      exitWith code
+    Right result ->
+      return result
+
+-- | Inner parse options. The arguments are similar to 'parseOpts',
+-- but it returns either a 'Left' composed of exit code and message,
+-- or a 'Right' for the success case.
+parseOptsInner :: (StandardOptions a) =>
+                  a
+               -> [String]
+               -> String
+               -> [GenericOptType a]
+               -> Either (ExitCode, String) (a, [String])
+parseOptsInner defaults argv progname options  =
+  case getOpt Permute options argv of
+    (opts, args, []) ->
+      case foldM (flip id) defaults opts of
+           Bad msg -> Left (ExitFailure 1,
+                            "Error while parsing command line arguments:\n"
+                            ++ msg ++ "\n")
+           Ok parsed ->
+             select (Right (parsed, args))
+                 [ (helpRequested parsed,
+                    Left (ExitSuccess, usageHelp progname options))
+                 , (verRequested parsed,
+                    Left (ExitSuccess, versionInfo progname))
+                 ]
+    (_, _, errs) ->
+      Left (ExitFailure 2, "Command line error: "  ++ concat errs ++ "\n" ++
+            usageHelp progname options)
diff --git a/htools/Ganeti/Daemon.hs b/htools/Ganeti/Daemon.hs
index 8fe3a83fc407283bb040c5f45daabe142adcc6e2..2296ef010497da01e0179df76b883fba62dc30b4 100644
--- a/htools/Ganeti/Daemon.hs
+++ b/htools/Ganeti/Daemon.hs
@@ -45,7 +45,6 @@ module Ganeti.Daemon
 import Control.Exception
 import Control.Monad
 import Data.Maybe (fromMaybe)
-import qualified Data.Version
 import Data.Word
 import GHC.IO.Handle (hDuplicateTo)
 import qualified Network.Socket as Socket
@@ -53,7 +52,6 @@ import Prelude hiding (catch)
 import System.Console.GetOpt
 import System.Exit
 import System.Environment
-import System.Info
 import System.IO
 import System.IO.Error (isDoesNotExistError)
 import System.Posix.Directory
@@ -62,13 +60,12 @@ import System.Posix.IO
 import System.Posix.Process
 import System.Posix.Types
 import System.Posix.Signals
-import Text.Printf
 
+import Ganeti.Common as Common
 import Ganeti.Logging
 import Ganeti.Runtime
 import Ganeti.BasicTypes
 import Ganeti.HTools.Utils
-import qualified Ganeti.Version as Version (version)
 import qualified Ganeti.Constants as C
 import qualified Ganeti.Ssconf as Ssconf
 
@@ -105,32 +102,17 @@ defaultOptions  = DaemonOptions
   , optSyslogUsage  = Nothing
   }
 
+instance StandardOptions DaemonOptions where
+  helpRequested = optShowHelp
+  verRequested  = optShowVer
+  requestHelp   = \opts -> opts { optShowHelp = True }
+  requestVer    = \opts -> opts { optShowVer  = True }
+
 -- | 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
+type OptType = GenericOptType DaemonOptions
 
 -- * 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}))
@@ -167,36 +149,11 @@ oSyslogUsage = Option "" ["syslog"]
                 \messages); one of 'no', 'yes' or 'only' [" ++ C.syslogUsage ++
                 "]")
 
--- | 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 <-
-          exitIfBad "Error while parsing command line arguments" $
-          foldM (flip id) defaultOptions opt_list
-        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
+  parseOpts defaultOptions cmd_args cmd options
 
 -- * Daemon-related functions
 -- | PID file mode.
@@ -321,16 +278,6 @@ genericMain daemon options main = do
   let progname = daemonName daemon
   (opts, args) <- parseArgs progname options
 
-  when (optShowHelp opts) $ do
-    putStr $ usageHelp progname options
-    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 ()
-    exitSuccess
-
   exitUnless (null args) "This program doesn't take any arguments"
 
   unless (optNoUserChecks opts) $ do
diff --git a/htools/Ganeti/HTools/CLI.hs b/htools/Ganeti/HTools/CLI.hs
index 6f437c77fd7410a71596b81711e5c79baf1e0240..ec2a299d7d17d03d85740f576513c3b7f57a9ef8 100644
--- a/htools/Ganeti/HTools/CLI.hs
+++ b/htools/Ganeti/HTools/CLI.hs
@@ -30,7 +30,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 module Ganeti.HTools.CLI
   ( Options(..)
   , OptType
-  , parseOpts
+  , defaultOptions
+  , Ganeti.HTools.CLI.parseOpts
   , parseOptsInner
   , parseYesNo
   , parseISpecString
@@ -73,13 +74,11 @@ module Ganeti.HTools.CLI
   , oPrintNodes
   , oQuiet
   , oRapiMaster
-  , oReplay
   , oSaveCluster
   , oSelInst
   , oShowHelp
   , oShowVer
   , oStdSpec
-  , oTestCount
   , oTieredSpec
   , oVerbose
   ) where
@@ -87,20 +86,17 @@ module Ganeti.HTools.CLI
 import Control.Monad
 import Data.Char (toUpper)
 import Data.Maybe (fromMaybe)
-import qualified Data.Version
 import System.Console.GetOpt
 import System.IO
-import System.Info
-import System.Exit
 import Text.Printf (printf)
 
-import qualified Ganeti.Version as Version (version)
 import qualified Ganeti.HTools.Container as Container
 import qualified Ganeti.HTools.Node as Node
 import qualified Ganeti.Constants as C
 import Ganeti.HTools.Types
 import Ganeti.HTools.Utils
 import Ganeti.BasicTypes
+import Ganeti.Common as Common
 
 -- * Constants
 
@@ -198,7 +194,13 @@ defaultOptions  = Options
   }
 
 -- | Abrreviation for the option type.
-type OptType = OptDescr (Options -> Result Options)
+type OptType = GenericOptType Options
+
+instance StandardOptions Options where
+  helpRequested = optShowHelp
+  verRequested  = optShowVer
+  requestHelp   = \opts -> opts { optShowHelp = True }
+  requestVer    = \opts -> opts { optShowVer  = True }
 
 -- * Helper functions
 
@@ -232,19 +234,18 @@ oDiskMoves = Option "" ["no-disk-moves"]
 
 oDiskTemplate :: OptType
 oDiskTemplate = Option "" ["disk-template"]
-                (ReqArg (\ t opts -> do
-                           dt <- diskTemplateFromRaw t
-                           return $ opts { optDiskTemplate = Just dt })
+                (reqWithConversion diskTemplateFromRaw
+                 (\dt opts -> Ok opts { optDiskTemplate = Just dt })
                  "TEMPLATE") "select the desired disk template"
 
 oSpindleUse :: OptType
 oSpindleUse = Option "" ["spindle-use"]
-              (ReqArg (\ n opts -> do
-                         su <- tryRead "parsing spindle-use" n
-                         when (su < 0) $
-                              fail "Invalid value of the spindle-use\
-                                   \ (expected >= 0)"
-                         return $ opts { optSpindleUse = Just su })
+              (reqWithConversion (tryRead "parsing spindle-use")
+               (\su opts -> do
+                  when (su < 0) $
+                       fail "Invalid value of the spindle-use\
+                            \ (expected >= 0)"
+                  return $ opts { optSpindleUse = Just su })
                "SPINDLES") "select how many virtual spindle instances use\
                            \ [default read from cluster]"
 
@@ -314,40 +315,45 @@ oMachineReadable = Option "" ["machine-readable"]
 
 oMaxCpu :: OptType
 oMaxCpu = Option "" ["max-cpu"]
-          (ReqArg (\ n opts -> do
-                     mcpu <- tryRead "parsing max-cpu" n
-                     when (mcpu <= 0) $
-                          fail "Invalid value of the max-cpu ratio,\
-                               \ expected >0"
-                     return $ opts { optMcpu = Just mcpu }) "RATIO")
+          (reqWithConversion (tryRead "parsing max-cpu")
+           (\mcpu opts -> do
+              when (mcpu <= 0) $
+                   fail "Invalid value of the max-cpu ratio,\
+                        \ expected >0"
+              return $ opts { optMcpu = Just mcpu }) "RATIO")
           "maximum virtual-to-physical cpu ratio for nodes (from 0\
           \ upwards) [default read from cluster]"
 
 oMaxSolLength :: OptType
 oMaxSolLength = Option "l" ["max-length"]
-                (ReqArg (\ i opts -> Ok opts { optMaxLength = read i }) "N")
+                (reqWithConversion (tryRead "max solution length")
+                 (\i opts -> Ok opts { optMaxLength = i }) "N")
                 "cap the solution at this many balancing or allocation \
                 \ rounds (useful for very unbalanced clusters or empty \
                 \ clusters)"
 
 oMinDisk :: OptType
 oMinDisk = Option "" ["min-disk"]
-           (ReqArg (\ n opts -> Ok opts { optMdsk = read n }) "RATIO")
+           (reqWithConversion (tryRead "min free disk space")
+            (\n opts -> Ok opts { optMdsk = n }) "RATIO")
            "minimum free disk space for nodes (between 0 and 1) [0]"
 
 oMinGain :: OptType
 oMinGain = Option "g" ["min-gain"]
-            (ReqArg (\ g opts -> Ok opts { optMinGain = read g }) "DELTA")
+           (reqWithConversion (tryRead "min gain")
+            (\g opts -> Ok opts { optMinGain = g }) "DELTA")
             "minimum gain to aim for in a balancing step before giving up"
 
 oMinGainLim :: OptType
 oMinGainLim = Option "" ["min-gain-limit"]
-            (ReqArg (\ g opts -> Ok opts { optMinGainLim = read g }) "SCORE")
+            (reqWithConversion (tryRead "min gain limit")
+             (\g opts -> Ok opts { optMinGainLim = g }) "SCORE")
             "minimum cluster score for which we start checking the min-gain"
 
 oMinScore :: OptType
 oMinScore = Option "e" ["min-score"]
-            (ReqArg (\ e opts -> Ok opts { optMinScore = read e }) "EPSILON")
+            (reqWithConversion (tryRead "min score")
+             (\e opts -> Ok opts { optMinScore = e }) "EPSILON")
             "mininum score to aim for"
 
 oNoHeaders :: OptType
@@ -416,16 +422,6 @@ oSaveCluster = Option "S" ["save"]
             (ReqArg (\ f opts -> Ok opts { optSaveCluster = Just f }) "FILE")
             "Save cluster state at the end of the processing to FILE"
 
-oShowHelp :: OptType
-oShowHelp = Option "h" ["help"]
-            (NoArg (\ opts -> Ok opts { optShowHelp = True}))
-            "show help"
-
-oShowVer :: OptType
-oShowVer = Option "V" ["version"]
-           (NoArg (\ opts -> Ok opts { optShowVer = True}))
-           "show the version of the program"
-
 oStdSpec :: OptType
 oStdSpec = Option "" ["standard-alloc"]
              (ReqArg (\ inp opts -> do
@@ -434,14 +430,6 @@ oStdSpec = Option "" ["standard-alloc"]
               "STDSPEC")
              "enable standard specs allocation, given as 'disk,ram,cpu'"
 
-oTestCount :: OptType
-oTestCount = Option "" ["test-count"]
-             (ReqArg (\ inp opts -> do
-                        tcount <- tryRead "parsing test count" inp
-                        return $ opts { optTestCount = Just tcount } )
-              "COUNT")
-             "override the target test count"
-
 oTieredSpec :: OptType
 oTieredSpec = Option "" ["tiered-alloc"]
              (ReqArg (\ inp opts -> do
@@ -450,11 +438,6 @@ oTieredSpec = Option "" ["tiered-alloc"]
               "TSPEC")
              "enable tiered specs allocation, given as 'disk,ram,cpu'"
 
-oReplay :: OptType
-oReplay = Option "" ["replay"]
-          (ReqArg (\ stat opts -> Ok opts { optReplay = Just stat } ) "STATE")
-          "Pre-seed the random number generator with STATE"
-
 oVerbose :: OptType
 oVerbose = Option "v" ["verbose"]
            (NoArg (\ opts -> Ok opts { optVerbose = optVerbose opts + 1 }))
@@ -462,64 +445,14 @@ oVerbose = Option "v" ["verbose"]
 
 -- * Functions
 
--- | Helper for parsing a yes\/no command line flag.
-parseYesNo :: Bool         -- ^ Default value (when we get a @Nothing@)
-           -> Maybe String -- ^ Parameter value
-           -> Result Bool  -- ^ Resulting boolean value
-parseYesNo v Nothing      = return v
-parseYesNo _ (Just "yes") = return True
-parseYesNo _ (Just "no")  = return False
-parseYesNo _ (Just s)     = fail ("Invalid choice '" ++ s ++
-                                  "', pass one of 'yes' or 'no'")
-
--- | Usage info.
-usageHelp :: String -> [OptType] -> String
-usageHelp progname =
-  usageInfo (printf "%s %s\nUsage: %s [OPTION...]"
-             progname Version.version progname)
-
--- | Show the program version info.
-versionInfo :: String -> String
-versionInfo progname =
-  printf "%s %s\ncompiled with %s %s\nrunning on %s %s\n"
-         progname Version.version compilerName
-         (Data.Version.showVersion compilerVersion)
-         os arch
-
--- | Command line parser, using the 'Options' structure.
+-- | Wrapper over 'Common.parseOpts' with our custom options.
 parseOpts :: [String]               -- ^ The command line arguments
           -> String                 -- ^ The program name
           -> [OptType]              -- ^ The supported command line options
           -> IO (Options, [String]) -- ^ The resulting options and leftover
                                     -- arguments
-parseOpts argv progname options =
-  case parseOptsInner argv progname options of
-    Left (code, msg) -> do
-      hPutStr (if code == 0 then stdout else stderr) msg
-      exitWith (if code == 0 then ExitSuccess else ExitFailure code)
-    Right result ->
-      return result
-
--- | Inner parse options. The arguments are similar to 'parseOpts',
--- but it returns either a 'Left' composed of exit code and message,
--- or a 'Right' for the success case.
-parseOptsInner :: [String] -> String -> [OptType]
-               -> Either (Int, String) (Options, [String])
-parseOptsInner argv progname options =
-  case getOpt Permute options argv of
-    (o, n, []) ->
-      let (pr, args) = (foldM (flip id) defaultOptions o, n)
-      in case pr of
-           Bad msg -> Left (1, "Error while parsing command\
-                               \line arguments:\n" ++ msg ++ "\n")
-           Ok po ->
-             select (Right (po, args))
-                 [ (optShowHelp po, Left (0, usageHelp progname options))
-                 , (optShowVer po,  Left (0, versionInfo progname))
-                 ]
-    (_, _, errs) ->
-      Left (2, "Command line error: "  ++ concat errs ++ "\n" ++
-            usageHelp progname options)
+parseOpts = Common.parseOpts defaultOptions
+
 
 -- | A shell script template for autogenerated scripts.
 shTemplate :: String
@@ -546,7 +479,6 @@ maybePrintNodes (Just fields) msg fn = do
   hPutStrLn stderr (msg ++ " status:")
   hPutStrLn stderr $ fn fields
 
-
 -- | Optionally print the instance list.
 maybePrintInsts :: Bool   -- ^ Whether to print the instance list
                 -> String -- ^ Type of the instance map (e.g. initial)
@@ -571,13 +503,14 @@ maybeShowWarnings fix_msgs =
 printKeys :: String              -- ^ Prefix to printed variables
           -> [(String, String)]  -- ^ List of (key, value) pairs to be printed
           -> IO ()
-printKeys prefix = mapM_ (\(k, v) ->
-                       printf "%s_%s=%s\n" prefix (map toUpper k) (ensureQuoted v))
+printKeys prefix =
+  mapM_ (\(k, v) ->
+           printf "%s_%s=%s\n" prefix (map toUpper k) (ensureQuoted v))
 
 -- | Prints the final @OK@ marker in machine readable output.
 printFinal :: String    -- ^ Prefix to printed variable
-           -> Bool      -- ^ Whether output should be machine readable
-                        -- Note: if not, there is nothing to print
+           -> Bool      -- ^ Whether output should be machine readable;
+                        -- note: if not, there is nothing to print
            -> IO ()
 printFinal prefix True =
   -- this should be the final entry