1 {-| Utility functions. -}
5 Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
61 , setOwnerAndGroupFromNames
64 import Data.Char (toUpper, isAlphaNum, isDigit, isSpace)
65 import Data.Function (on)
67 import qualified Data.Map as M
68 import Control.Monad (foldM)
73 import Ganeti.BasicTypes
74 import qualified Ganeti.ConstantUtils as ConstantUtils
79 import System.Posix.Files
84 -- | To be used only for debugging, breaks referential integrity.
85 debug :: Show a => a -> a
86 debug x = trace (show x) x
88 -- | Displays a modified form of the second parameter before returning
90 debugFn :: Show b => (a -> b) -> a -> a
91 debugFn fn x = debug (fn x) `seq` x
93 -- | Show the first parameter before returning the second one.
94 debugXy :: Show a => a -> b -> b
99 -- | Apply the function if condition holds, otherwise use default value.
100 applyIf :: Bool -> (a -> a) -> a -> a
101 applyIf b f x = if b then f x else x
103 -- | Comma-join a string list.
104 commaJoin :: [String] -> String
105 commaJoin = intercalate ","
107 -- | Split a list on a separator and return an array.
108 sepSplit :: Eq a => a -> [a] -> [[a]]
113 | otherwise = x:sepSplit sep ys
114 where (x, xs) = break (== sep) s
117 -- | Simple pluralize helper
118 plural :: Int -> String -> String -> String
122 -- | Ensure a value is quoted if needed.
123 ensureQuoted :: String -> String
124 ensureQuoted v = if not (all (\c -> isAlphaNum c || c == '.') v)
128 -- * Mathematical functions
130 -- Simple and slow statistical functions, please replace with better
133 -- | Standard deviation function.
134 stdDev :: [Double] -> Double
136 -- first, calculate the list length and sum lst in a single step,
137 -- for performance reasons
138 let (ll', sx) = foldl' (\(rl, rs) e ->
141 in rl' `seq` rs' `seq` (rl', rs')) (0::Int, 0) lst
142 ll = fromIntegral ll'::Double
144 av = foldl' (\accu em -> let d = em - mv in accu + d * d) 0.0 lst
145 in sqrt (av / ll) -- stddev
147 -- * Logical functions
149 -- Avoid syntactic sugar and enhance readability. These functions are proposed
150 -- by some for inclusion in the Prelude, and at the moment they are present
151 -- (with various definitions) in the utility-ht package. Some rationale and
152 -- discussion is available at <http://www.haskell.org/haskellwiki/If-then-else>
154 -- | \"if\" as a function, rather than as syntactic sugar.
155 if' :: Bool -- ^ condition
156 -> a -- ^ \"then\" result
157 -> a -- ^ \"else\" result
158 -> a -- ^ \"then\" or "else" result depending on the condition
162 -- * Parsing utility functions
164 -- | Parse results from readsPrec.
165 parseChoices :: (Monad m, Read a) => String -> String -> [(a, String)] -> m a
166 parseChoices _ _ ((v, ""):[]) = return v
167 parseChoices name s ((_, e):[]) =
168 fail $ name ++ ": leftover characters when parsing '"
169 ++ s ++ "': '" ++ e ++ "'"
170 parseChoices name s _ = fail $ name ++ ": cannot parse string '" ++ s ++ "'"
172 -- | Safe 'read' function returning data encapsulated in a Result.
173 tryRead :: (Monad m, Read a) => String -> String -> m a
174 tryRead name s = parseChoices name s $ reads s
176 -- | Format a table of strings to maintain consistent length.
177 formatTable :: [[String]] -> [Bool] -> [[String]]
178 formatTable vals numpos =
179 let vtrans = transpose vals -- transpose, so that we work on rows
180 -- rather than columns
181 mlens = map (maximum . map length) vtrans
182 expnd = map (\(flds, isnum, ml) ->
184 let delta = ml - length val
185 filler = replicate delta ' '
192 ) (zip3 vtrans numpos mlens)
195 -- | Constructs a printable table from given header and rows
196 printTable :: String -> [String] -> [[String]] -> [Bool] -> String
197 printTable lp header rows isnum =
198 unlines . map ((++) lp . (:) ' ' . unwords) $
199 formatTable (header:rows) isnum
201 -- | Converts a unit (e.g. m or GB) into a scaling factor.
202 parseUnitValue :: (Monad m) => String -> m Rational
204 -- binary conversions first
205 | null unit = return 1
206 | unit == "m" || upper == "MIB" = return 1
207 | unit == "g" || upper == "GIB" = return kbBinary
208 | unit == "t" || upper == "TIB" = return $ kbBinary * kbBinary
210 | unit == "M" || upper == "MB" = return mbFactor
211 | unit == "G" || upper == "GB" = return $ mbFactor * kbDecimal
212 | unit == "T" || upper == "TB" = return $ mbFactor * kbDecimal * kbDecimal
213 | otherwise = fail $ "Unknown unit '" ++ unit ++ "'"
214 where upper = map toUpper unit
215 kbBinary = 1024 :: Rational
216 kbDecimal = 1000 :: Rational
217 decToBin = kbDecimal / kbBinary -- factor for 1K conversion
218 mbFactor = decToBin * decToBin -- twice the factor for just 1K
220 -- | Tries to extract number and scale from the given string.
222 -- Input must be in the format NUMBER+ SPACE* [UNIT]. If no unit is
223 -- specified, it defaults to MiB. Return value is always an integral
225 parseUnit :: (Monad m, Integral a, Read a) => String -> m a
227 -- TODO: enhance this by splitting the unit parsing code out and
228 -- accepting floating-point numbers
229 case (reads str::[(Int, String)]) of
231 let unit = dropWhile (== ' ') suffix
233 scaling <- parseUnitValue unit
234 return $ truncate (fromIntegral v * scaling)
235 _ -> fail $ "Can't parse string '" ++ str ++ "'"
237 -- | Unwraps a 'Result', exiting the program if it is a 'Bad' value,
238 -- otherwise returning the actual contained value.
239 exitIfBad :: String -> Result a -> IO a
240 exitIfBad msg (Bad s) = exitErr (msg ++ ": " ++ s)
241 exitIfBad _ (Ok v) = return v
243 -- | Exits immediately with an error message.
244 exitErr :: String -> IO a
246 hPutStrLn stderr $ "Error: " ++ errmsg
247 exitWith (ExitFailure 1)
249 -- | Exits with an error message if the given boolean condition if true.
250 exitWhen :: Bool -> String -> IO ()
251 exitWhen True msg = exitErr msg
252 exitWhen False _ = return ()
254 -- | Exits with an error message /unless/ the given boolean condition
255 -- if true, the opposite of 'exitWhen'.
256 exitUnless :: Bool -> String -> IO ()
257 exitUnless cond = exitWhen (not cond)
259 -- | Unwraps a 'Result', logging a warning message and then returning a default
260 -- value if it is a 'Bad' value, otherwise returning the actual contained value.
261 logWarningIfBad :: String -> a -> Result a -> IO a
262 logWarningIfBad msg defVal (Bad s) = do
263 logWarning $ msg ++ ": " ++ s
265 logWarningIfBad _ _ (Ok v) = return v
267 -- | Print a warning, but do not exit.
268 warn :: String -> IO ()
269 warn = hPutStrLn stderr . (++) "Warning: "
271 -- | Helper for 'niceSort'. Computes the key element for a given string.
272 extractKey :: [Either Integer String] -- ^ Current (partial) key, reversed
273 -> String -- ^ Remaining string
274 -> ([Either Integer String], String)
275 extractKey ek [] = (reverse ek, [])
276 extractKey ek xs@(x:_) =
277 let (span_fn, conv_fn) = if isDigit x
278 then (isDigit, Left . read)
279 else (not . isDigit, Right)
280 (k, rest) = span span_fn xs
281 in extractKey (conv_fn k:ek) rest
283 {-| Sort a list of strings based on digit and non-digit groupings.
285 Given a list of names @['a1', 'a10', 'a11', 'a2']@ this function
286 will sort the list in the logical order @['a1', 'a2', 'a10', 'a11']@.
288 The sort algorithm breaks each name in groups of either only-digits or
289 no-digits, and sorts based on each group.
291 Internally, this is not implemented via regexes (like the Python
292 version), but via actual splitting of the string in sequences of
293 either digits or everything else, and converting the digit sequences
294 in /Left Integer/ and the non-digit ones in /Right String/, at which
295 point sorting becomes trivial due to the built-in 'Either' ordering;
296 we only need one extra step of dropping the key at the end.
299 niceSort :: [String] -> [String]
300 niceSort = niceSortKey id
302 -- | Key-version of 'niceSort'. We use 'sortBy' and @compare `on` fst@
303 -- since we don't want to add an ordering constraint on the /a/ type,
304 -- hence the need to only compare the first element of the /(key, a)/
306 niceSortKey :: (a -> String) -> [a] -> [a]
308 map snd . sortBy (compare `on` fst) .
309 map (\s -> (fst . extractKey [] $ keyfn s, s))
311 -- | Strip space characthers (including newline). As this is
312 -- expensive, should only be run on small strings.
313 rStripSpace :: String -> String
314 rStripSpace = reverse . dropWhile isSpace . reverse
316 -- | Returns a random UUID.
317 -- This is a Linux-specific method as it uses the /proc filesystem.
320 contents <- readFile ConstantUtils.randomUuidFile
321 return $! rStripSpace $ take 128 contents
323 -- | Returns the current time as an 'Integer' representing the number
324 -- of seconds from the Unix epoch.
325 getCurrentTime :: IO Integer
327 TOD ctime _ <- getClockTime
330 -- | Returns the current time as an 'Integer' representing the number
331 -- of microseconds from the Unix epoch (hence the need for 'Integer').
332 getCurrentTimeUSec :: IO Integer
333 getCurrentTimeUSec = do
334 TOD ctime pico <- getClockTime
335 -- pico: 10^-12, micro: 10^-6, so we have to shift seconds left and
337 return $ ctime * 1000000 + pico `div` 1000000
339 -- | Convert a ClockTime into a (seconds-only) timestamp.
340 clockTimeToString :: ClockTime -> String
341 clockTimeToString (TOD t _) = show t
343 {-| Strip a prefix from a string, allowing the last character of the prefix
344 (which is assumed to be a separator) to be absent from the string if the string
347 \>>> chompPrefix \"foo:bar:\" \"a:b:c\"
350 \>>> chompPrefix \"foo:bar:\" \"foo:bar:baz\"
353 \>>> chompPrefix \"foo:bar:\" \"foo:bar:\"
356 \>>> chompPrefix \"foo:bar:\" \"foo:bar\"
359 \>>> chompPrefix \"foo:bar:\" \"foo:barbaz\"
362 chompPrefix :: String -> String -> Maybe String
363 chompPrefix pfx str =
364 if pfx `isPrefixOf` str || str == init pfx
365 then Just $ drop (length pfx) str
368 -- | Breaks a string in lines with length \<= maxWidth.
370 -- NOTE: The split is OK if:
372 -- * It doesn't break a word, i.e. the next line begins with space
373 -- (@isSpace . head $ rest@) or the current line ends with space
374 -- (@null revExtra@);
376 -- * It breaks a very big word that doesn't fit anyway (@null revLine@).
377 wrap :: Int -- ^ maxWidth
378 -> String -- ^ string that needs wrapping
379 -> [String] -- ^ string \"broken\" in lines
380 wrap maxWidth = filter (not . null) . map trim . wrap0
381 where wrap0 :: String -> [String]
383 | length text <= maxWidth = [text]
384 | isSplitOK = line : wrap0 rest
385 | otherwise = line' : wrap0 rest'
386 where (line, rest) = splitAt maxWidth text
387 (revExtra, revLine) = break isSpace . reverse $ line
388 (line', rest') = (reverse revLine, reverse revExtra ++ rest)
390 null revLine || null revExtra || startsWithSpace rest
391 startsWithSpace (x:_) = isSpace x
392 startsWithSpace _ = False
394 -- | Removes surrounding whitespace. Should only be used in small
396 trim :: String -> String
397 trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace
399 -- | A safer head version, with a default value.
400 defaultHead :: a -> [a] -> a
401 defaultHead def [] = def
402 defaultHead _ (x:_) = x
404 -- | A 'head' version in the I/O monad, for validating parameters
405 -- without which we cannot continue.
406 exitIfEmpty :: String -> [a] -> IO a
407 exitIfEmpty _ (x:_) = return x
408 exitIfEmpty s [] = exitErr s
410 -- | Split an 'Either' list into two separate lists (containing the
411 -- 'Left' and 'Right' elements, plus a \"trail\" list that allows
412 -- recombination later.
414 -- This is splitter; for recombination, look at 'recombineEithers'.
415 -- The sum of \"left\" and \"right\" lists should be equal to the
416 -- original list length, and the trail list should be the same length
417 -- as well. The entries in the resulting lists are reversed in
418 -- comparison with the original list.
419 splitEithers :: [Either a b] -> ([a], [b], [Bool])
420 splitEithers = foldl' splitter ([], [], [])
421 where splitter (l, r, t) e =
423 Left v -> (v:l, r, False:t)
424 Right v -> (l, v:r, True:t)
426 -- | Recombines two \"left\" and \"right\" lists using a \"trail\"
427 -- list into a single 'Either' list.
429 -- This is the counterpart to 'splitEithers'. It does the opposite
430 -- transformation, and the output list will be the reverse of the
431 -- input lists. Since 'splitEithers' also reverses the lists, calling
432 -- these together will result in the original list.
434 -- Mismatches in the structure of the lists (e.g. inconsistent
435 -- lengths) are represented via 'Bad'; normally this function should
436 -- not fail, if lists are passed as generated by 'splitEithers'.
437 recombineEithers :: (Show a, Show b) =>
438 [a] -> [b] -> [Bool] -> Result [Either a b]
439 recombineEithers lefts rights trail =
440 foldM recombiner ([], lefts, rights) trail >>= checker
441 where checker (eithers, [], []) = Ok eithers
442 checker (_, lefts', rights') =
443 Bad $ "Inconsistent results after recombination, l'=" ++
444 show lefts' ++ ", r'=" ++ show rights'
445 recombiner (es, l:ls, rs) False = Ok (Left l:es, ls, rs)
446 recombiner (es, ls, r:rs) True = Ok (Right r:es, ls, rs)
447 recombiner (_, ls, rs) t = Bad $ "Inconsistent trail log: l=" ++
448 show ls ++ ", r=" ++ show rs ++ ",t=" ++
451 -- | Default hints for the resolver
452 resolveAddrHints :: Maybe AddrInfo
454 Just defaultHints { addrFlags = [AI_NUMERICHOST, AI_NUMERICSERV] }
456 -- | Resolves a numeric address.
457 resolveAddr :: Int -> String -> IO (Result (Family, SockAddr))
458 resolveAddr port str = do
459 resolved <- getAddrInfo resolveAddrHints (Just str) (Just (show port))
460 return $ case resolved of
461 [] -> Bad "Invalid results from lookup?"
462 best:_ -> Ok (addrFamily best, addrAddress best)
464 -- | Set the owner and the group of a file (given as names, not numeric id).
465 setOwnerAndGroupFromNames :: FilePath -> GanetiDaemon -> GanetiGroup -> IO ()
466 setOwnerAndGroupFromNames filename daemon dGroup = do
467 -- TODO: it would be nice to rework this (or getEnts) so that runtimeEnts
468 -- is read only once per daemon startup, and then cached for further usage.
469 runtimeEnts <- getEnts
470 ents <- exitIfBad "Can't find required user/groups" runtimeEnts
471 -- note: we use directly ! as lookup failures shouldn't happen, due
472 -- to the map construction
473 let uid = fst ents M.! daemon
474 let gid = snd ents M.! dGroup
475 setOwnerAndGroup filename uid gid