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
60 , setOwnerAndGroupFromNames
63 import Data.Char (toUpper, isAlphaNum, isDigit, isSpace)
64 import Data.Function (on)
66 import qualified Data.Map as M
67 import Control.Monad (foldM)
71 import Ganeti.BasicTypes
72 import qualified Ganeti.Constants as C
77 import System.Posix.Files
82 -- | To be used only for debugging, breaks referential integrity.
83 debug :: Show a => a -> a
84 debug x = trace (show x) x
86 -- | Displays a modified form of the second parameter before returning
88 debugFn :: Show b => (a -> b) -> a -> a
89 debugFn fn x = debug (fn x) `seq` x
91 -- | Show the first parameter before returning the second one.
92 debugXy :: Show a => a -> b -> b
97 -- | Apply the function if condition holds, otherwise use default value.
98 applyIf :: Bool -> (a -> a) -> a -> a
99 applyIf b f x = if b then f x else x
101 -- | Comma-join a string list.
102 commaJoin :: [String] -> String
103 commaJoin = intercalate ","
105 -- | Split a list on a separator and return an array.
106 sepSplit :: Eq a => a -> [a] -> [[a]]
111 | otherwise = x:sepSplit sep ys
112 where (x, xs) = break (== sep) s
115 -- | Simple pluralize helper
116 plural :: Int -> String -> String -> String
120 -- | Ensure a value is quoted if needed.
121 ensureQuoted :: String -> String
122 ensureQuoted v = if not (all (\c -> isAlphaNum c || c == '.') v)
126 -- * Mathematical functions
128 -- Simple and slow statistical functions, please replace with better
131 -- | Standard deviation function.
132 stdDev :: [Double] -> Double
134 -- first, calculate the list length and sum lst in a single step,
135 -- for performance reasons
136 let (ll', sx) = foldl' (\(rl, rs) e ->
139 in rl' `seq` rs' `seq` (rl', rs')) (0::Int, 0) lst
140 ll = fromIntegral ll'::Double
142 av = foldl' (\accu em -> let d = em - mv in accu + d * d) 0.0 lst
143 in sqrt (av / ll) -- stddev
145 -- * Logical functions
147 -- Avoid syntactic sugar and enhance readability. These functions are proposed
148 -- by some for inclusion in the Prelude, and at the moment they are present
149 -- (with various definitions) in the utility-ht package. Some rationale and
150 -- discussion is available at <http://www.haskell.org/haskellwiki/If-then-else>
152 -- | \"if\" as a function, rather than as syntactic sugar.
153 if' :: Bool -- ^ condition
154 -> a -- ^ \"then\" result
155 -> a -- ^ \"else\" result
156 -> a -- ^ \"then\" or "else" result depending on the condition
160 -- * Parsing utility functions
162 -- | Parse results from readsPrec.
163 parseChoices :: (Monad m, Read a) => String -> String -> [(a, String)] -> m a
164 parseChoices _ _ ((v, ""):[]) = return v
165 parseChoices name s ((_, e):[]) =
166 fail $ name ++ ": leftover characters when parsing '"
167 ++ s ++ "': '" ++ e ++ "'"
168 parseChoices name s _ = fail $ name ++ ": cannot parse string '" ++ s ++ "'"
170 -- | Safe 'read' function returning data encapsulated in a Result.
171 tryRead :: (Monad m, Read a) => String -> String -> m a
172 tryRead name s = parseChoices name s $ reads s
174 -- | Format a table of strings to maintain consistent length.
175 formatTable :: [[String]] -> [Bool] -> [[String]]
176 formatTable vals numpos =
177 let vtrans = transpose vals -- transpose, so that we work on rows
178 -- rather than columns
179 mlens = map (maximum . map length) vtrans
180 expnd = map (\(flds, isnum, ml) ->
182 let delta = ml - length val
183 filler = replicate delta ' '
190 ) (zip3 vtrans numpos mlens)
193 -- | Constructs a printable table from given header and rows
194 printTable :: String -> [String] -> [[String]] -> [Bool] -> String
195 printTable lp header rows isnum =
196 unlines . map ((++) lp . (:) ' ' . unwords) $
197 formatTable (header:rows) isnum
199 -- | Converts a unit (e.g. m or GB) into a scaling factor.
200 parseUnitValue :: (Monad m) => String -> m Rational
202 -- binary conversions first
203 | null unit = return 1
204 | unit == "m" || upper == "MIB" = return 1
205 | unit == "g" || upper == "GIB" = return kbBinary
206 | unit == "t" || upper == "TIB" = return $ kbBinary * kbBinary
208 | unit == "M" || upper == "MB" = return mbFactor
209 | unit == "G" || upper == "GB" = return $ mbFactor * kbDecimal
210 | unit == "T" || upper == "TB" = return $ mbFactor * kbDecimal * kbDecimal
211 | otherwise = fail $ "Unknown unit '" ++ unit ++ "'"
212 where upper = map toUpper unit
213 kbBinary = 1024 :: Rational
214 kbDecimal = 1000 :: Rational
215 decToBin = kbDecimal / kbBinary -- factor for 1K conversion
216 mbFactor = decToBin * decToBin -- twice the factor for just 1K
218 -- | Tries to extract number and scale from the given string.
220 -- Input must be in the format NUMBER+ SPACE* [UNIT]. If no unit is
221 -- specified, it defaults to MiB. Return value is always an integral
223 parseUnit :: (Monad m, Integral a, Read a) => String -> m a
225 -- TODO: enhance this by splitting the unit parsing code out and
226 -- accepting floating-point numbers
227 case (reads str::[(Int, String)]) of
229 let unit = dropWhile (== ' ') suffix
231 scaling <- parseUnitValue unit
232 return $ truncate (fromIntegral v * scaling)
233 _ -> fail $ "Can't parse string '" ++ str ++ "'"
235 -- | Unwraps a 'Result', exiting the program if it is a 'Bad' value,
236 -- otherwise returning the actual contained value.
237 exitIfBad :: String -> Result a -> IO a
238 exitIfBad msg (Bad s) = exitErr (msg ++ ": " ++ s)
239 exitIfBad _ (Ok v) = return v
241 -- | Exits immediately with an error message.
242 exitErr :: String -> IO a
244 hPutStrLn stderr $ "Error: " ++ errmsg
245 exitWith (ExitFailure 1)
247 -- | Exits with an error message if the given boolean condition if true.
248 exitWhen :: Bool -> String -> IO ()
249 exitWhen True msg = exitErr msg
250 exitWhen False _ = return ()
252 -- | Exits with an error message /unless/ the given boolean condition
253 -- if true, the opposite of 'exitWhen'.
254 exitUnless :: Bool -> String -> IO ()
255 exitUnless cond = exitWhen (not cond)
257 -- | Unwraps a 'Result', logging a warning message and then returning a default
258 -- value if it is a 'Bad' value, otherwise returning the actual contained value.
259 logWarningIfBad :: String -> a -> Result a -> IO a
260 logWarningIfBad msg defVal (Bad s) = do
261 logWarning $ msg ++ ": " ++ s
263 logWarningIfBad _ _ (Ok v) = return v
265 -- | Print a warning, but do not exit.
266 warn :: String -> IO ()
267 warn = hPutStrLn stderr . (++) "Warning: "
269 -- | Helper for 'niceSort'. Computes the key element for a given string.
270 extractKey :: [Either Integer String] -- ^ Current (partial) key, reversed
271 -> String -- ^ Remaining string
272 -> ([Either Integer String], String)
273 extractKey ek [] = (reverse ek, [])
274 extractKey ek xs@(x:_) =
275 let (span_fn, conv_fn) = if isDigit x
276 then (isDigit, Left . read)
277 else (not . isDigit, Right)
278 (k, rest) = span span_fn xs
279 in extractKey (conv_fn k:ek) rest
281 {-| Sort a list of strings based on digit and non-digit groupings.
283 Given a list of names @['a1', 'a10', 'a11', 'a2']@ this function
284 will sort the list in the logical order @['a1', 'a2', 'a10', 'a11']@.
286 The sort algorithm breaks each name in groups of either only-digits or
287 no-digits, and sorts based on each group.
289 Internally, this is not implemented via regexes (like the Python
290 version), but via actual splitting of the string in sequences of
291 either digits or everything else, and converting the digit sequences
292 in /Left Integer/ and the non-digit ones in /Right String/, at which
293 point sorting becomes trivial due to the built-in 'Either' ordering;
294 we only need one extra step of dropping the key at the end.
297 niceSort :: [String] -> [String]
298 niceSort = niceSortKey id
300 -- | Key-version of 'niceSort'. We use 'sortBy' and @compare `on` fst@
301 -- since we don't want to add an ordering constraint on the /a/ type,
302 -- hence the need to only compare the first element of the /(key, a)/
304 niceSortKey :: (a -> String) -> [a] -> [a]
306 map snd . sortBy (compare `on` fst) .
307 map (\s -> (fst . extractKey [] $ keyfn s, s))
309 -- | Strip space characthers (including newline). As this is
310 -- expensive, should only be run on small strings.
311 rStripSpace :: String -> String
312 rStripSpace = reverse . dropWhile isSpace . reverse
314 -- | Returns a random UUID.
315 -- This is a Linux-specific method as it uses the /proc filesystem.
318 contents <- readFile C.randomUuidFile
319 return $! rStripSpace $ take 128 contents
321 -- | Returns the current time as an 'Integer' representing the number
322 -- of seconds from the Unix epoch.
323 getCurrentTime :: IO Integer
325 TOD ctime _ <- getClockTime
328 -- | Returns the current time as an 'Integer' representing the number
329 -- of microseconds from the Unix epoch (hence the need for 'Integer').
330 getCurrentTimeUSec :: IO Integer
331 getCurrentTimeUSec = do
332 TOD ctime pico <- getClockTime
333 -- pico: 10^-12, micro: 10^-6, so we have to shift seconds left and
335 return $ ctime * 1000000 + pico `div` 1000000
337 -- | Convert a ClockTime into a (seconds-only) timestamp.
338 clockTimeToString :: ClockTime -> String
339 clockTimeToString (TOD t _) = show t
341 {-| Strip a prefix from a string, allowing the last character of the prefix
342 (which is assumed to be a separator) to be absent from the string if the string
345 \>>> chompPrefix \"foo:bar:\" \"a:b:c\"
348 \>>> chompPrefix \"foo:bar:\" \"foo:bar:baz\"
351 \>>> chompPrefix \"foo:bar:\" \"foo:bar:\"
354 \>>> chompPrefix \"foo:bar:\" \"foo:bar\"
357 \>>> chompPrefix \"foo:bar:\" \"foo:barbaz\"
360 chompPrefix :: String -> String -> Maybe String
361 chompPrefix pfx str =
362 if pfx `isPrefixOf` str || str == init pfx
363 then Just $ drop (length pfx) str
366 -- | Breaks a string in lines with length \<= maxWidth.
368 -- NOTE: The split is OK if:
370 -- * It doesn't break a word, i.e. the next line begins with space
371 -- (@isSpace . head $ rest@) or the current line ends with space
372 -- (@null revExtra@);
374 -- * It breaks a very big word that doesn't fit anyway (@null revLine@).
375 wrap :: Int -- ^ maxWidth
376 -> String -- ^ string that needs wrapping
377 -> [String] -- ^ string \"broken\" in lines
378 wrap maxWidth = filter (not . null) . map trim . wrap0
379 where wrap0 :: String -> [String]
381 | length text <= maxWidth = [text]
382 | isSplitOK = line : wrap0 rest
383 | otherwise = line' : wrap0 rest'
384 where (line, rest) = splitAt maxWidth text
385 (revExtra, revLine) = break isSpace . reverse $ line
386 (line', rest') = (reverse revLine, reverse revExtra ++ rest)
388 null revLine || null revExtra || startsWithSpace rest
389 startsWithSpace (x:_) = isSpace x
390 startsWithSpace _ = False
392 -- | Removes surrounding whitespace. Should only be used in small
394 trim :: String -> String
395 trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace
397 -- | A safer head version, with a default value.
398 defaultHead :: a -> [a] -> a
399 defaultHead def [] = def
400 defaultHead _ (x:_) = x
402 -- | A 'head' version in the I/O monad, for validating parameters
403 -- without which we cannot continue.
404 exitIfEmpty :: String -> [a] -> IO a
405 exitIfEmpty _ (x:_) = return x
406 exitIfEmpty s [] = exitErr s
408 -- | Split an 'Either' list into two separate lists (containing the
409 -- 'Left' and 'Right' elements, plus a \"trail\" list that allows
410 -- recombination later.
412 -- This is splitter; for recombination, look at 'recombineEithers'.
413 -- The sum of \"left\" and \"right\" lists should be equal to the
414 -- original list length, and the trail list should be the same length
415 -- as well. The entries in the resulting lists are reversed in
416 -- comparison with the original list.
417 splitEithers :: [Either a b] -> ([a], [b], [Bool])
418 splitEithers = foldl' splitter ([], [], [])
419 where splitter (l, r, t) e =
421 Left v -> (v:l, r, False:t)
422 Right v -> (l, v:r, True:t)
424 -- | Recombines two \"left\" and \"right\" lists using a \"trail\"
425 -- list into a single 'Either' list.
427 -- This is the counterpart to 'splitEithers'. It does the opposite
428 -- transformation, and the output list will be the reverse of the
429 -- input lists. Since 'splitEithers' also reverses the lists, calling
430 -- these together will result in the original list.
432 -- Mismatches in the structure of the lists (e.g. inconsistent
433 -- lengths) are represented via 'Bad'; normally this function should
434 -- not fail, if lists are passed as generated by 'splitEithers'.
435 recombineEithers :: (Show a, Show b) =>
436 [a] -> [b] -> [Bool] -> Result [Either a b]
437 recombineEithers lefts rights trail =
438 foldM recombiner ([], lefts, rights) trail >>= checker
439 where checker (eithers, [], []) = Ok eithers
440 checker (_, lefts', rights') =
441 Bad $ "Inconsistent results after recombination, l'=" ++
442 show lefts' ++ ", r'=" ++ show rights'
443 recombiner (es, l:ls, rs) False = Ok (Left l:es, ls, rs)
444 recombiner (es, ls, r:rs) True = Ok (Right r:es, ls, rs)
445 recombiner (_, ls, rs) t = Bad $ "Inconsistent trail log: l=" ++
446 show ls ++ ", r=" ++ show rs ++ ",t=" ++
449 -- | Set the owner and the group of a file (given as names, not numeric id).
450 setOwnerAndGroupFromNames :: FilePath -> GanetiDaemon -> GanetiGroup -> IO ()
451 setOwnerAndGroupFromNames filename daemon dGroup = do
452 -- TODO: it would be nice to rework this (or getEnts) so that runtimeEnts
453 -- is read only once per daemon startup, and then cached for further usage.
454 runtimeEnts <- getEnts
455 ents <- exitIfBad "Can't find required user/groups" runtimeEnts
456 -- note: we use directly ! as lookup failures shouldn't happen, due
457 -- to the map construction
458 let uid = fst ents M.! daemon
459 let gid = snd ents M.! dGroup
460 setOwnerAndGroup filename uid gid