Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / Utils.hs @ 6fd8ceff

History | View | Annotate | Download (19.3 kB)

1 525bfb36 Iustin Pop
{-| Utility functions. -}
2 e4f08c46 Iustin Pop
3 e2fa2baf Iustin Pop
{-
4 e2fa2baf Iustin Pop
5 a7f0953a Iustin Pop
Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
6 e2fa2baf Iustin Pop
7 e2fa2baf Iustin Pop
This program is free software; you can redistribute it and/or modify
8 e2fa2baf Iustin Pop
it under the terms of the GNU General Public License as published by
9 e2fa2baf Iustin Pop
the Free Software Foundation; either version 2 of the License, or
10 e2fa2baf Iustin Pop
(at your option) any later version.
11 e2fa2baf Iustin Pop
12 e2fa2baf Iustin Pop
This program is distributed in the hope that it will be useful, but
13 e2fa2baf Iustin Pop
WITHOUT ANY WARRANTY; without even the implied warranty of
14 e2fa2baf Iustin Pop
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 e2fa2baf Iustin Pop
General Public License for more details.
16 e2fa2baf Iustin Pop
17 e2fa2baf Iustin Pop
You should have received a copy of the GNU General Public License
18 e2fa2baf Iustin Pop
along with this program; if not, write to the Free Software
19 e2fa2baf Iustin Pop
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 e2fa2baf Iustin Pop
02110-1301, USA.
21 e2fa2baf Iustin Pop
22 e2fa2baf Iustin Pop
-}
23 e2fa2baf Iustin Pop
24 26d62e4c Iustin Pop
module Ganeti.Utils
25 ebf38064 Iustin Pop
  ( debug
26 ebf38064 Iustin Pop
  , debugFn
27 ebf38064 Iustin Pop
  , debugXy
28 ebf38064 Iustin Pop
  , sepSplit
29 ebf38064 Iustin Pop
  , stdDev
30 ebf38064 Iustin Pop
  , if'
31 ebf38064 Iustin Pop
  , select
32 ebf38064 Iustin Pop
  , applyIf
33 ebf38064 Iustin Pop
  , commaJoin
34 79eef90b Agata Murawska
  , ensureQuoted
35 ebf38064 Iustin Pop
  , tryRead
36 ebf38064 Iustin Pop
  , formatTable
37 c3024b7e René Nussbaumer
  , printTable
38 ebf38064 Iustin Pop
  , parseUnit
39 19e310cc René Nussbaumer
  , plural
40 04edfc99 Iustin Pop
  , niceSort
41 04edfc99 Iustin Pop
  , niceSortKey
42 88a10df5 Iustin Pop
  , exitIfBad
43 88a10df5 Iustin Pop
  , exitErr
44 88a10df5 Iustin Pop
  , exitWhen
45 88a10df5 Iustin Pop
  , exitUnless
46 838393d1 Michele Tartara
  , logWarningIfBad
47 256e28c4 Iustin Pop
  , rStripSpace
48 80a0546b Michele Tartara
  , newUUID
49 ace37e24 Michele Tartara
  , getCurrentTime
50 a6e054a8 Iustin Pop
  , getCurrentTimeUSec
51 b6aeda4a Dato Simó
  , clockTimeToString
52 b009f682 Dato Simó
  , chompPrefix
53 7dbe4c72 Klaus Aehlig
  , warn
54 9fb621af Yiannis Tsiouris
  , wrap
55 9fb621af Yiannis Tsiouris
  , trim
56 72747d91 Iustin Pop
  , defaultHead
57 72747d91 Iustin Pop
  , exitIfEmpty
58 da9e2aff Iustin Pop
  , splitEithers
59 da9e2aff Iustin Pop
  , recombineEithers
60 986a8671 Michele Tartara
  , resolveAddr
61 bf7ee7ad Klaus Aehlig
  , monadicThe
62 a39cd547 Michele Tartara
  , setOwnerAndGroupFromNames
63 88b58ed6 Hrvoje Ribicic
  , formatOrdinal
64 710f5ae2 Klaus Aehlig
  , atomicWriteFile
65 5badb3e7 Klaus Aehlig
  , tryAndLogIOError
66 32b07c5f Klaus Aehlig
  , lockFile
67 6fd8ceff Klaus Aehlig
  , FStat
68 6fd8ceff Klaus Aehlig
  , nullFStat
69 6fd8ceff Klaus Aehlig
  , needsReload
70 ebf38064 Iustin Pop
  ) where
71 e4f08c46 Iustin Pop
72 5badb3e7 Klaus Aehlig
import Control.Exception (try)
73 256e28c4 Iustin Pop
import Data.Char (toUpper, isAlphaNum, isDigit, isSpace)
74 04edfc99 Iustin Pop
import Data.Function (on)
75 29ac5975 Iustin Pop
import Data.List
76 a39cd547 Michele Tartara
import qualified Data.Map as M
77 32b07c5f Klaus Aehlig
import Control.Monad (foldM, liftM)
78 710f5ae2 Klaus Aehlig
import System.Directory (renameFile)
79 710f5ae2 Klaus Aehlig
import System.FilePath.Posix (takeDirectory, takeBaseName)
80 6fd8ceff Klaus Aehlig
import System.Posix.Types
81 e4f08c46 Iustin Pop
82 e4f08c46 Iustin Pop
import Debug.Trace
83 986a8671 Michele Tartara
import Network.Socket
84 e4f08c46 Iustin Pop
85 88a10df5 Iustin Pop
import Ganeti.BasicTypes
86 06fd57e5 Jose A. Lopes
import qualified Ganeti.ConstantUtils as ConstantUtils
87 838393d1 Michele Tartara
import Ganeti.Logging
88 a39cd547 Michele Tartara
import Ganeti.Runtime
89 88a10df5 Iustin Pop
import System.IO
90 88a10df5 Iustin Pop
import System.Exit
91 a39cd547 Michele Tartara
import System.Posix.Files
92 32b07c5f Klaus Aehlig
import System.Posix.IO
93 b6aeda4a Dato Simó
import System.Time
94 88a10df5 Iustin Pop
95 9188aeef Iustin Pop
-- * Debug functions
96 9188aeef Iustin Pop
97 e4f08c46 Iustin Pop
-- | To be used only for debugging, breaks referential integrity.
98 e4f08c46 Iustin Pop
debug :: Show a => a -> a
99 e4f08c46 Iustin Pop
debug x = trace (show x) x
100 e4f08c46 Iustin Pop
101 525bfb36 Iustin Pop
-- | Displays a modified form of the second parameter before returning
102 525bfb36 Iustin Pop
-- it.
103 adc5c176 Iustin Pop
debugFn :: Show b => (a -> b) -> a -> a
104 adc5c176 Iustin Pop
debugFn fn x = debug (fn x) `seq` x
105 adc5c176 Iustin Pop
106 525bfb36 Iustin Pop
-- | Show the first parameter before returning the second one.
107 adc5c176 Iustin Pop
debugXy :: Show a => a -> b -> b
108 05ff7a00 Agata Murawska
debugXy = seq . debug
109 adc5c176 Iustin Pop
110 525bfb36 Iustin Pop
-- * Miscellaneous
111 1b7cf8ca Iustin Pop
112 61bbbed7 Agata Murawska
-- | Apply the function if condition holds, otherwise use default value.
113 61bbbed7 Agata Murawska
applyIf :: Bool -> (a -> a) -> a -> a
114 61bbbed7 Agata Murawska
applyIf b f x = if b then f x else x
115 61bbbed7 Agata Murawska
116 e4f08c46 Iustin Pop
-- | Comma-join a string list.
117 e4f08c46 Iustin Pop
commaJoin :: [String] -> String
118 e4f08c46 Iustin Pop
commaJoin = intercalate ","
119 e4f08c46 Iustin Pop
120 748d5d50 Iustin Pop
-- | Split a list on a separator and return an array.
121 748d5d50 Iustin Pop
sepSplit :: Eq a => a -> [a] -> [[a]]
122 e4f08c46 Iustin Pop
sepSplit sep s
123 ebf38064 Iustin Pop
  | null s    = []
124 ebf38064 Iustin Pop
  | null xs   = [x]
125 ebf38064 Iustin Pop
  | null ys   = [x,[]]
126 ebf38064 Iustin Pop
  | otherwise = x:sepSplit sep ys
127 ebf38064 Iustin Pop
  where (x, xs) = break (== sep) s
128 ebf38064 Iustin Pop
        ys = drop 1 xs
129 e4f08c46 Iustin Pop
130 19e310cc René Nussbaumer
-- | Simple pluralize helper
131 19e310cc René Nussbaumer
plural :: Int -> String -> String -> String
132 19e310cc René Nussbaumer
plural 1 s _ = s
133 19e310cc René Nussbaumer
plural _ _ p = p
134 19e310cc René Nussbaumer
135 79eef90b Agata Murawska
-- | Ensure a value is quoted if needed.
136 79eef90b Agata Murawska
ensureQuoted :: String -> String
137 79eef90b Agata Murawska
ensureQuoted v = if not (all (\c -> isAlphaNum c || c == '.') v)
138 79eef90b Agata Murawska
                 then '\'':v ++ "'"
139 79eef90b Agata Murawska
                 else v
140 79eef90b Agata Murawska
141 9188aeef Iustin Pop
-- * Mathematical functions
142 9188aeef Iustin Pop
143 185297fa Iustin Pop
-- Simple and slow statistical functions, please replace with better
144 185297fa Iustin Pop
-- versions
145 e4f08c46 Iustin Pop
146 525bfb36 Iustin Pop
-- | Standard deviation function.
147 4715711d Iustin Pop
stdDev :: [Double] -> Double
148 4715711d Iustin Pop
stdDev lst =
149 7570569e Iustin Pop
  -- first, calculate the list length and sum lst in a single step,
150 7570569e Iustin Pop
  -- for performance reasons
151 7570569e Iustin Pop
  let (ll', sx) = foldl' (\(rl, rs) e ->
152 7570569e Iustin Pop
                           let rl' = rl + 1
153 7570569e Iustin Pop
                               rs' = rs + e
154 7570569e Iustin Pop
                           in rl' `seq` rs' `seq` (rl', rs')) (0::Int, 0) lst
155 7570569e Iustin Pop
      ll = fromIntegral ll'::Double
156 7570569e Iustin Pop
      mv = sx / ll
157 7570569e Iustin Pop
      av = foldl' (\accu em -> let d = em - mv in accu + d * d) 0.0 lst
158 4715711d Iustin Pop
  in sqrt (av / ll) -- stddev
159 dd4c56ed Iustin Pop
160 bfe6c954 Guido Trotter
-- *  Logical functions
161 bfe6c954 Guido Trotter
162 bfe6c954 Guido Trotter
-- Avoid syntactic sugar and enhance readability. These functions are proposed
163 bfe6c954 Guido Trotter
-- by some for inclusion in the Prelude, and at the moment they are present
164 bfe6c954 Guido Trotter
-- (with various definitions) in the utility-ht package. Some rationale and
165 bfe6c954 Guido Trotter
-- discussion is available at <http://www.haskell.org/haskellwiki/If-then-else>
166 bfe6c954 Guido Trotter
167 bfe6c954 Guido Trotter
-- | \"if\" as a function, rather than as syntactic sugar.
168 bfe6c954 Guido Trotter
if' :: Bool -- ^ condition
169 bfe6c954 Guido Trotter
    -> a    -- ^ \"then\" result
170 bfe6c954 Guido Trotter
    -> a    -- ^ \"else\" result
171 bfe6c954 Guido Trotter
    -> a    -- ^ \"then\" or "else" result depending on the condition
172 bfe6c954 Guido Trotter
if' True x _ = x
173 bfe6c954 Guido Trotter
if' _    _ y = y
174 bfe6c954 Guido Trotter
175 5b763470 Iustin Pop
-- * Parsing utility functions
176 5b763470 Iustin Pop
177 525bfb36 Iustin Pop
-- | Parse results from readsPrec.
178 5b763470 Iustin Pop
parseChoices :: (Monad m, Read a) => String -> String -> [(a, String)] -> m a
179 5b763470 Iustin Pop
parseChoices _ _ ((v, ""):[]) = return v
180 5b763470 Iustin Pop
parseChoices name s ((_, e):[]) =
181 5b763470 Iustin Pop
    fail $ name ++ ": leftover characters when parsing '"
182 5b763470 Iustin Pop
           ++ s ++ "': '" ++ e ++ "'"
183 5b763470 Iustin Pop
parseChoices name s _ = fail $ name ++ ": cannot parse string '" ++ s ++ "'"
184 5b763470 Iustin Pop
185 5b763470 Iustin Pop
-- | Safe 'read' function returning data encapsulated in a Result.
186 5b763470 Iustin Pop
tryRead :: (Monad m, Read a) => String -> String -> m a
187 5b763470 Iustin Pop
tryRead name s = parseChoices name s $ reads s
188 c5f7412e Iustin Pop
189 525bfb36 Iustin Pop
-- | Format a table of strings to maintain consistent length.
190 c5f7412e Iustin Pop
formatTable :: [[String]] -> [Bool] -> [[String]]
191 c5f7412e Iustin Pop
formatTable vals numpos =
192 c5f7412e Iustin Pop
    let vtrans = transpose vals  -- transpose, so that we work on rows
193 c5f7412e Iustin Pop
                                 -- rather than columns
194 c5f7412e Iustin Pop
        mlens = map (maximum . map length) vtrans
195 c5f7412e Iustin Pop
        expnd = map (\(flds, isnum, ml) ->
196 c5f7412e Iustin Pop
                         map (\val ->
197 c5f7412e Iustin Pop
                                  let delta = ml - length val
198 c5f7412e Iustin Pop
                                      filler = replicate delta ' '
199 c5f7412e Iustin Pop
                                  in if delta > 0
200 c5f7412e Iustin Pop
                                     then if isnum
201 c5f7412e Iustin Pop
                                          then filler ++ val
202 c5f7412e Iustin Pop
                                          else val ++ filler
203 c5f7412e Iustin Pop
                                     else val
204 c5f7412e Iustin Pop
                             ) flds
205 c5f7412e Iustin Pop
                    ) (zip3 vtrans numpos mlens)
206 c5f7412e Iustin Pop
   in transpose expnd
207 9b9da389 Iustin Pop
208 c3024b7e René Nussbaumer
-- | Constructs a printable table from given header and rows
209 c3024b7e René Nussbaumer
printTable :: String -> [String] -> [[String]] -> [Bool] -> String
210 c3024b7e René Nussbaumer
printTable lp header rows isnum =
211 2cdaf225 Iustin Pop
  unlines . map ((++) lp . (:) ' ' . unwords) $
212 c3024b7e René Nussbaumer
  formatTable (header:rows) isnum
213 c3024b7e René Nussbaumer
214 1cdcf8f3 Iustin Pop
-- | Converts a unit (e.g. m or GB) into a scaling factor.
215 1cdcf8f3 Iustin Pop
parseUnitValue :: (Monad m) => String -> m Rational
216 1cdcf8f3 Iustin Pop
parseUnitValue unit
217 1cdcf8f3 Iustin Pop
  -- binary conversions first
218 1cdcf8f3 Iustin Pop
  | null unit                     = return 1
219 1cdcf8f3 Iustin Pop
  | unit == "m" || upper == "MIB" = return 1
220 1cdcf8f3 Iustin Pop
  | unit == "g" || upper == "GIB" = return kbBinary
221 1cdcf8f3 Iustin Pop
  | unit == "t" || upper == "TIB" = return $ kbBinary * kbBinary
222 1cdcf8f3 Iustin Pop
  -- SI conversions
223 1cdcf8f3 Iustin Pop
  | unit == "M" || upper == "MB"  = return mbFactor
224 1cdcf8f3 Iustin Pop
  | unit == "G" || upper == "GB"  = return $ mbFactor * kbDecimal
225 1cdcf8f3 Iustin Pop
  | unit == "T" || upper == "TB"  = return $ mbFactor * kbDecimal * kbDecimal
226 1cdcf8f3 Iustin Pop
  | otherwise = fail $ "Unknown unit '" ++ unit ++ "'"
227 1cdcf8f3 Iustin Pop
  where upper = map toUpper unit
228 5850e990 Iustin Pop
        kbBinary = 1024 :: Rational
229 5850e990 Iustin Pop
        kbDecimal = 1000 :: Rational
230 1cdcf8f3 Iustin Pop
        decToBin = kbDecimal / kbBinary -- factor for 1K conversion
231 1cdcf8f3 Iustin Pop
        mbFactor = decToBin * decToBin -- twice the factor for just 1K
232 1cdcf8f3 Iustin Pop
233 1cb92fac Iustin Pop
-- | Tries to extract number and scale from the given string.
234 1cb92fac Iustin Pop
--
235 1cb92fac Iustin Pop
-- Input must be in the format NUMBER+ SPACE* [UNIT]. If no unit is
236 1cb92fac Iustin Pop
-- specified, it defaults to MiB. Return value is always an integral
237 1cb92fac Iustin Pop
-- value in MiB.
238 1cb92fac Iustin Pop
parseUnit :: (Monad m, Integral a, Read a) => String -> m a
239 1cb92fac Iustin Pop
parseUnit str =
240 ebf38064 Iustin Pop
  -- TODO: enhance this by splitting the unit parsing code out and
241 ebf38064 Iustin Pop
  -- accepting floating-point numbers
242 1cdcf8f3 Iustin Pop
  case (reads str::[(Int, String)]) of
243 ebf38064 Iustin Pop
    [(v, suffix)] ->
244 ebf38064 Iustin Pop
      let unit = dropWhile (== ' ') suffix
245 1cdcf8f3 Iustin Pop
      in do
246 1cdcf8f3 Iustin Pop
        scaling <- parseUnitValue unit
247 1cdcf8f3 Iustin Pop
        return $ truncate (fromIntegral v * scaling)
248 ebf38064 Iustin Pop
    _ -> fail $ "Can't parse string '" ++ str ++ "'"
249 88a10df5 Iustin Pop
250 88a10df5 Iustin Pop
-- | Unwraps a 'Result', exiting the program if it is a 'Bad' value,
251 88a10df5 Iustin Pop
-- otherwise returning the actual contained value.
252 88a10df5 Iustin Pop
exitIfBad :: String -> Result a -> IO a
253 707cd3d7 Helga Velroyen
exitIfBad msg (Bad s) = exitErr (msg ++ ": " ++ s)
254 88a10df5 Iustin Pop
exitIfBad _ (Ok v) = return v
255 88a10df5 Iustin Pop
256 88a10df5 Iustin Pop
-- | Exits immediately with an error message.
257 88a10df5 Iustin Pop
exitErr :: String -> IO a
258 88a10df5 Iustin Pop
exitErr errmsg = do
259 707cd3d7 Helga Velroyen
  hPutStrLn stderr $ "Error: " ++ errmsg
260 88a10df5 Iustin Pop
  exitWith (ExitFailure 1)
261 88a10df5 Iustin Pop
262 88a10df5 Iustin Pop
-- | Exits with an error message if the given boolean condition if true.
263 88a10df5 Iustin Pop
exitWhen :: Bool -> String -> IO ()
264 88a10df5 Iustin Pop
exitWhen True msg = exitErr msg
265 88a10df5 Iustin Pop
exitWhen False _  = return ()
266 88a10df5 Iustin Pop
267 88a10df5 Iustin Pop
-- | Exits with an error message /unless/ the given boolean condition
268 88a10df5 Iustin Pop
-- if true, the opposite of 'exitWhen'.
269 88a10df5 Iustin Pop
exitUnless :: Bool -> String -> IO ()
270 88a10df5 Iustin Pop
exitUnless cond = exitWhen (not cond)
271 04edfc99 Iustin Pop
272 838393d1 Michele Tartara
-- | Unwraps a 'Result', logging a warning message and then returning a default
273 838393d1 Michele Tartara
-- value if it is a 'Bad' value, otherwise returning the actual contained value.
274 838393d1 Michele Tartara
logWarningIfBad :: String -> a -> Result a -> IO a
275 838393d1 Michele Tartara
logWarningIfBad msg defVal (Bad s) = do
276 838393d1 Michele Tartara
  logWarning $ msg ++ ": " ++ s
277 838393d1 Michele Tartara
  return defVal
278 838393d1 Michele Tartara
logWarningIfBad _ _ (Ok v) = return v
279 838393d1 Michele Tartara
280 5badb3e7 Klaus Aehlig
-- | Try an IO interaction, log errors and unfold as a 'Result'.
281 5badb3e7 Klaus Aehlig
tryAndLogIOError :: IO a -> String -> (a -> Result b) -> IO (Result b)
282 5badb3e7 Klaus Aehlig
tryAndLogIOError io msg okfn =
283 5badb3e7 Klaus Aehlig
 try io >>= either
284 5badb3e7 Klaus Aehlig
   (\ e -> do
285 5badb3e7 Klaus Aehlig
       let combinedmsg = msg ++ ": " ++ show (e :: IOError)
286 5badb3e7 Klaus Aehlig
       logError combinedmsg
287 5badb3e7 Klaus Aehlig
       return . Bad $ combinedmsg)
288 5badb3e7 Klaus Aehlig
   (return . okfn)
289 5badb3e7 Klaus Aehlig
290 7dbe4c72 Klaus Aehlig
-- | Print a warning, but do not exit.
291 7dbe4c72 Klaus Aehlig
warn :: String -> IO ()
292 7dbe4c72 Klaus Aehlig
warn = hPutStrLn stderr . (++) "Warning: "
293 7dbe4c72 Klaus Aehlig
294 04edfc99 Iustin Pop
-- | Helper for 'niceSort'. Computes the key element for a given string.
295 04edfc99 Iustin Pop
extractKey :: [Either Integer String]  -- ^ Current (partial) key, reversed
296 04edfc99 Iustin Pop
           -> String                   -- ^ Remaining string
297 04edfc99 Iustin Pop
           -> ([Either Integer String], String)
298 04edfc99 Iustin Pop
extractKey ek [] = (reverse ek, [])
299 04edfc99 Iustin Pop
extractKey ek xs@(x:_) =
300 04edfc99 Iustin Pop
  let (span_fn, conv_fn) = if isDigit x
301 04edfc99 Iustin Pop
                             then (isDigit, Left . read)
302 04edfc99 Iustin Pop
                             else (not . isDigit, Right)
303 04edfc99 Iustin Pop
      (k, rest) = span span_fn xs
304 04edfc99 Iustin Pop
  in extractKey (conv_fn k:ek) rest
305 04edfc99 Iustin Pop
306 04edfc99 Iustin Pop
{-| Sort a list of strings based on digit and non-digit groupings.
307 04edfc99 Iustin Pop
308 04edfc99 Iustin Pop
Given a list of names @['a1', 'a10', 'a11', 'a2']@ this function
309 04edfc99 Iustin Pop
will sort the list in the logical order @['a1', 'a2', 'a10', 'a11']@.
310 04edfc99 Iustin Pop
311 04edfc99 Iustin Pop
The sort algorithm breaks each name in groups of either only-digits or
312 04edfc99 Iustin Pop
no-digits, and sorts based on each group.
313 04edfc99 Iustin Pop
314 04edfc99 Iustin Pop
Internally, this is not implemented via regexes (like the Python
315 04edfc99 Iustin Pop
version), but via actual splitting of the string in sequences of
316 04edfc99 Iustin Pop
either digits or everything else, and converting the digit sequences
317 04edfc99 Iustin Pop
in /Left Integer/ and the non-digit ones in /Right String/, at which
318 04edfc99 Iustin Pop
point sorting becomes trivial due to the built-in 'Either' ordering;
319 04edfc99 Iustin Pop
we only need one extra step of dropping the key at the end.
320 04edfc99 Iustin Pop
321 04edfc99 Iustin Pop
-}
322 04edfc99 Iustin Pop
niceSort :: [String] -> [String]
323 a7f0953a Iustin Pop
niceSort = niceSortKey id
324 04edfc99 Iustin Pop
325 04edfc99 Iustin Pop
-- | Key-version of 'niceSort'. We use 'sortBy' and @compare `on` fst@
326 04edfc99 Iustin Pop
-- since we don't want to add an ordering constraint on the /a/ type,
327 04edfc99 Iustin Pop
-- hence the need to only compare the first element of the /(key, a)/
328 04edfc99 Iustin Pop
-- tuple.
329 04edfc99 Iustin Pop
niceSortKey :: (a -> String) -> [a] -> [a]
330 04edfc99 Iustin Pop
niceSortKey keyfn =
331 04edfc99 Iustin Pop
  map snd . sortBy (compare `on` fst) .
332 04edfc99 Iustin Pop
  map (\s -> (fst . extractKey [] $ keyfn s, s))
333 256e28c4 Iustin Pop
334 256e28c4 Iustin Pop
-- | Strip space characthers (including newline). As this is
335 256e28c4 Iustin Pop
-- expensive, should only be run on small strings.
336 256e28c4 Iustin Pop
rStripSpace :: String -> String
337 256e28c4 Iustin Pop
rStripSpace = reverse . dropWhile isSpace . reverse
338 80a0546b Michele Tartara
339 80a0546b Michele Tartara
-- | Returns a random UUID.
340 80a0546b Michele Tartara
-- This is a Linux-specific method as it uses the /proc filesystem.
341 80a0546b Michele Tartara
newUUID :: IO String
342 80a0546b Michele Tartara
newUUID = do
343 06fd57e5 Jose A. Lopes
  contents <- readFile ConstantUtils.randomUuidFile
344 37dfcacb Iustin Pop
  return $! rStripSpace $ take 128 contents
345 b6aeda4a Dato Simó
346 a6e054a8 Iustin Pop
-- | Returns the current time as an 'Integer' representing the number
347 a6e054a8 Iustin Pop
-- of seconds from the Unix epoch.
348 ace37e24 Michele Tartara
getCurrentTime :: IO Integer
349 ace37e24 Michele Tartara
getCurrentTime = do
350 ace37e24 Michele Tartara
  TOD ctime _ <- getClockTime
351 ace37e24 Michele Tartara
  return ctime
352 ace37e24 Michele Tartara
353 a6e054a8 Iustin Pop
-- | Returns the current time as an 'Integer' representing the number
354 a6e054a8 Iustin Pop
-- of microseconds from the Unix epoch (hence the need for 'Integer').
355 a6e054a8 Iustin Pop
getCurrentTimeUSec :: IO Integer
356 a6e054a8 Iustin Pop
getCurrentTimeUSec = do
357 a6e054a8 Iustin Pop
  TOD ctime pico <- getClockTime
358 a6e054a8 Iustin Pop
  -- pico: 10^-12, micro: 10^-6, so we have to shift seconds left and
359 a6e054a8 Iustin Pop
  -- picoseconds right
360 a6e054a8 Iustin Pop
  return $ ctime * 1000000 + pico `div` 1000000
361 a6e054a8 Iustin Pop
362 b6aeda4a Dato Simó
-- | Convert a ClockTime into a (seconds-only) timestamp.
363 b6aeda4a Dato Simó
clockTimeToString :: ClockTime -> String
364 b6aeda4a Dato Simó
clockTimeToString (TOD t _) = show t
365 b009f682 Dato Simó
366 b009f682 Dato Simó
{-| Strip a prefix from a string, allowing the last character of the prefix
367 b009f682 Dato Simó
(which is assumed to be a separator) to be absent from the string if the string
368 b009f682 Dato Simó
terminates there.
369 b009f682 Dato Simó
370 94042ae4 Michele Tartara
\>>> chompPrefix \"foo:bar:\" \"a:b:c\"
371 b009f682 Dato Simó
Nothing
372 b009f682 Dato Simó
373 94042ae4 Michele Tartara
\>>> chompPrefix \"foo:bar:\" \"foo:bar:baz\"
374 94042ae4 Michele Tartara
Just \"baz\"
375 b009f682 Dato Simó
376 94042ae4 Michele Tartara
\>>> chompPrefix \"foo:bar:\" \"foo:bar:\"
377 94042ae4 Michele Tartara
Just \"\"
378 b009f682 Dato Simó
379 94042ae4 Michele Tartara
\>>> chompPrefix \"foo:bar:\" \"foo:bar\"
380 94042ae4 Michele Tartara
Just \"\"
381 b009f682 Dato Simó
382 94042ae4 Michele Tartara
\>>> chompPrefix \"foo:bar:\" \"foo:barbaz\"
383 b009f682 Dato Simó
Nothing
384 b009f682 Dato Simó
-}
385 b009f682 Dato Simó
chompPrefix :: String -> String -> Maybe String
386 b009f682 Dato Simó
chompPrefix pfx str =
387 b009f682 Dato Simó
  if pfx `isPrefixOf` str || str == init pfx
388 b009f682 Dato Simó
    then Just $ drop (length pfx) str
389 b009f682 Dato Simó
    else Nothing
390 9fb621af Yiannis Tsiouris
391 9fb621af Yiannis Tsiouris
-- | Breaks a string in lines with length \<= maxWidth.
392 9fb621af Yiannis Tsiouris
--
393 9fb621af Yiannis Tsiouris
-- NOTE: The split is OK if:
394 9fb621af Yiannis Tsiouris
--
395 9fb621af Yiannis Tsiouris
-- * It doesn't break a word, i.e. the next line begins with space
396 9fb621af Yiannis Tsiouris
--   (@isSpace . head $ rest@) or the current line ends with space
397 9fb621af Yiannis Tsiouris
--   (@null revExtra@);
398 9fb621af Yiannis Tsiouris
--
399 9fb621af Yiannis Tsiouris
-- * It breaks a very big word that doesn't fit anyway (@null revLine@).
400 9fb621af Yiannis Tsiouris
wrap :: Int      -- ^ maxWidth
401 9fb621af Yiannis Tsiouris
     -> String   -- ^ string that needs wrapping
402 9fb621af Yiannis Tsiouris
     -> [String] -- ^ string \"broken\" in lines
403 9fb621af Yiannis Tsiouris
wrap maxWidth = filter (not . null) . map trim . wrap0
404 9fb621af Yiannis Tsiouris
  where wrap0 :: String -> [String]
405 9fb621af Yiannis Tsiouris
        wrap0 text
406 9fb621af Yiannis Tsiouris
          | length text <= maxWidth = [text]
407 9fb621af Yiannis Tsiouris
          | isSplitOK               = line : wrap0 rest
408 9fb621af Yiannis Tsiouris
          | otherwise               = line' : wrap0 rest'
409 9fb621af Yiannis Tsiouris
          where (line, rest) = splitAt maxWidth text
410 9fb621af Yiannis Tsiouris
                (revExtra, revLine) = break isSpace . reverse $ line
411 9fb621af Yiannis Tsiouris
                (line', rest') = (reverse revLine, reverse revExtra ++ rest)
412 9fb621af Yiannis Tsiouris
                isSplitOK =
413 9fb621af Yiannis Tsiouris
                  null revLine || null revExtra || startsWithSpace rest
414 9fb621af Yiannis Tsiouris
                startsWithSpace (x:_) = isSpace x
415 9fb621af Yiannis Tsiouris
                startsWithSpace _     = False
416 9fb621af Yiannis Tsiouris
417 9fb621af Yiannis Tsiouris
-- | Removes surrounding whitespace. Should only be used in small
418 9fb621af Yiannis Tsiouris
-- strings.
419 9fb621af Yiannis Tsiouris
trim :: String -> String
420 9fb621af Yiannis Tsiouris
trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace
421 72747d91 Iustin Pop
422 72747d91 Iustin Pop
-- | A safer head version, with a default value.
423 72747d91 Iustin Pop
defaultHead :: a -> [a] -> a
424 72747d91 Iustin Pop
defaultHead def []    = def
425 72747d91 Iustin Pop
defaultHead _   (x:_) = x
426 72747d91 Iustin Pop
427 72747d91 Iustin Pop
-- | A 'head' version in the I/O monad, for validating parameters
428 72747d91 Iustin Pop
-- without which we cannot continue.
429 72747d91 Iustin Pop
exitIfEmpty :: String -> [a] -> IO a
430 72747d91 Iustin Pop
exitIfEmpty _ (x:_) = return x
431 72747d91 Iustin Pop
exitIfEmpty s []    = exitErr s
432 da9e2aff Iustin Pop
433 bf7ee7ad Klaus Aehlig
-- | Obtain the unique element of a list in an arbitrary monad.
434 bf7ee7ad Klaus Aehlig
monadicThe :: (Eq a, Monad m) => String -> [a] -> m a
435 bf7ee7ad Klaus Aehlig
monadicThe s [] = fail s
436 bf7ee7ad Klaus Aehlig
monadicThe s (x:xs)
437 bf7ee7ad Klaus Aehlig
  | all (x ==) xs = return x
438 bf7ee7ad Klaus Aehlig
  | otherwise = fail s
439 bf7ee7ad Klaus Aehlig
440 da9e2aff Iustin Pop
-- | Split an 'Either' list into two separate lists (containing the
441 da9e2aff Iustin Pop
-- 'Left' and 'Right' elements, plus a \"trail\" list that allows
442 da9e2aff Iustin Pop
-- recombination later.
443 da9e2aff Iustin Pop
--
444 da9e2aff Iustin Pop
-- This is splitter; for recombination, look at 'recombineEithers'.
445 da9e2aff Iustin Pop
-- The sum of \"left\" and \"right\" lists should be equal to the
446 da9e2aff Iustin Pop
-- original list length, and the trail list should be the same length
447 da9e2aff Iustin Pop
-- as well. The entries in the resulting lists are reversed in
448 da9e2aff Iustin Pop
-- comparison with the original list.
449 da9e2aff Iustin Pop
splitEithers :: [Either a b] -> ([a], [b], [Bool])
450 da9e2aff Iustin Pop
splitEithers = foldl' splitter ([], [], [])
451 da9e2aff Iustin Pop
  where splitter (l, r, t) e =
452 da9e2aff Iustin Pop
          case e of
453 da9e2aff Iustin Pop
            Left  v -> (v:l, r, False:t)
454 da9e2aff Iustin Pop
            Right v -> (l, v:r, True:t)
455 da9e2aff Iustin Pop
456 da9e2aff Iustin Pop
-- | Recombines two \"left\" and \"right\" lists using a \"trail\"
457 da9e2aff Iustin Pop
-- list into a single 'Either' list.
458 da9e2aff Iustin Pop
--
459 da9e2aff Iustin Pop
-- This is the counterpart to 'splitEithers'. It does the opposite
460 da9e2aff Iustin Pop
-- transformation, and the output list will be the reverse of the
461 da9e2aff Iustin Pop
-- input lists. Since 'splitEithers' also reverses the lists, calling
462 da9e2aff Iustin Pop
-- these together will result in the original list.
463 da9e2aff Iustin Pop
--
464 da9e2aff Iustin Pop
-- Mismatches in the structure of the lists (e.g. inconsistent
465 da9e2aff Iustin Pop
-- lengths) are represented via 'Bad'; normally this function should
466 da9e2aff Iustin Pop
-- not fail, if lists are passed as generated by 'splitEithers'.
467 da9e2aff Iustin Pop
recombineEithers :: (Show a, Show b) =>
468 da9e2aff Iustin Pop
                    [a] -> [b] -> [Bool] -> Result [Either a b]
469 da9e2aff Iustin Pop
recombineEithers lefts rights trail =
470 da9e2aff Iustin Pop
  foldM recombiner ([], lefts, rights) trail >>= checker
471 da9e2aff Iustin Pop
    where checker (eithers, [], []) = Ok eithers
472 da9e2aff Iustin Pop
          checker (_, lefts', rights') =
473 da9e2aff Iustin Pop
            Bad $ "Inconsistent results after recombination, l'=" ++
474 da9e2aff Iustin Pop
                show lefts' ++ ", r'=" ++ show rights'
475 da9e2aff Iustin Pop
          recombiner (es, l:ls, rs) False = Ok (Left l:es,  ls, rs)
476 da9e2aff Iustin Pop
          recombiner (es, ls, r:rs) True  = Ok (Right r:es, ls, rs)
477 da9e2aff Iustin Pop
          recombiner (_,  ls, rs) t = Bad $ "Inconsistent trail log: l=" ++
478 da9e2aff Iustin Pop
                                      show ls ++ ", r=" ++ show rs ++ ",t=" ++
479 da9e2aff Iustin Pop
                                      show t
480 986a8671 Michele Tartara
481 986a8671 Michele Tartara
-- | Default hints for the resolver
482 986a8671 Michele Tartara
resolveAddrHints :: Maybe AddrInfo
483 986a8671 Michele Tartara
resolveAddrHints =
484 986a8671 Michele Tartara
  Just defaultHints { addrFlags = [AI_NUMERICHOST, AI_NUMERICSERV] }
485 986a8671 Michele Tartara
486 986a8671 Michele Tartara
-- | Resolves a numeric address.
487 986a8671 Michele Tartara
resolveAddr :: Int -> String -> IO (Result (Family, SockAddr))
488 986a8671 Michele Tartara
resolveAddr port str = do
489 986a8671 Michele Tartara
  resolved <- getAddrInfo resolveAddrHints (Just str) (Just (show port))
490 986a8671 Michele Tartara
  return $ case resolved of
491 986a8671 Michele Tartara
             [] -> Bad "Invalid results from lookup?"
492 986a8671 Michele Tartara
             best:_ -> Ok (addrFamily best, addrAddress best)
493 858ecf2b Klaus Aehlig
494 a39cd547 Michele Tartara
-- | Set the owner and the group of a file (given as names, not numeric id).
495 a39cd547 Michele Tartara
setOwnerAndGroupFromNames :: FilePath -> GanetiDaemon -> GanetiGroup -> IO ()
496 a39cd547 Michele Tartara
setOwnerAndGroupFromNames filename daemon dGroup = do
497 a39cd547 Michele Tartara
  -- TODO: it would be nice to rework this (or getEnts) so that runtimeEnts
498 a39cd547 Michele Tartara
  -- is read only once per daemon startup, and then cached for further usage.
499 a39cd547 Michele Tartara
  runtimeEnts <- getEnts
500 a39cd547 Michele Tartara
  ents <- exitIfBad "Can't find required user/groups" runtimeEnts
501 a39cd547 Michele Tartara
  -- note: we use directly ! as lookup failures shouldn't happen, due
502 a39cd547 Michele Tartara
  -- to the map construction
503 a39cd547 Michele Tartara
  let uid = fst ents M.! daemon
504 a39cd547 Michele Tartara
  let gid = snd ents M.! dGroup
505 a39cd547 Michele Tartara
  setOwnerAndGroup filename uid gid
506 88b58ed6 Hrvoje Ribicic
507 88b58ed6 Hrvoje Ribicic
-- | Formats an integral number, appending a suffix.
508 88b58ed6 Hrvoje Ribicic
formatOrdinal :: (Integral a, Show a) => a -> String
509 88b58ed6 Hrvoje Ribicic
formatOrdinal num
510 88b58ed6 Hrvoje Ribicic
  | num > 10 && num < 20 = suffix "th"
511 88b58ed6 Hrvoje Ribicic
  | tens == 1            = suffix "st"
512 88b58ed6 Hrvoje Ribicic
  | tens == 2            = suffix "nd"
513 88b58ed6 Hrvoje Ribicic
  | tens == 3            = suffix "rd"
514 88b58ed6 Hrvoje Ribicic
  | otherwise            = suffix "th"
515 88b58ed6 Hrvoje Ribicic
  where tens     = num `mod` 10
516 88b58ed6 Hrvoje Ribicic
        suffix s = show num ++ s
517 710f5ae2 Klaus Aehlig
518 710f5ae2 Klaus Aehlig
-- | Atomically write a file, by first writing the contents into a temporary
519 710f5ae2 Klaus Aehlig
-- file and then renaming it to the old position.
520 710f5ae2 Klaus Aehlig
atomicWriteFile :: FilePath -> String -> IO ()
521 710f5ae2 Klaus Aehlig
atomicWriteFile path contents = do
522 710f5ae2 Klaus Aehlig
  (tmppath, tmphandle) <- openTempFile (takeDirectory path) (takeBaseName path)
523 710f5ae2 Klaus Aehlig
  hPutStr tmphandle contents
524 710f5ae2 Klaus Aehlig
  hClose tmphandle
525 710f5ae2 Klaus Aehlig
  renameFile tmppath path
526 32b07c5f Klaus Aehlig
527 32b07c5f Klaus Aehlig
-- | Attempt, in a non-blocking way, to obtain a lock on a given file; report
528 32b07c5f Klaus Aehlig
-- back success.
529 32b07c5f Klaus Aehlig
lockFile :: FilePath -> IO (Result ())
530 32b07c5f Klaus Aehlig
lockFile path = do
531 32b07c5f Klaus Aehlig
  handle <- openFile path WriteMode
532 32b07c5f Klaus Aehlig
  fd <- handleToFd handle
533 32b07c5f Klaus Aehlig
  Control.Monad.liftM (either (Bad . show) Ok)
534 32b07c5f Klaus Aehlig
    (try (setLock fd (WriteLock, AbsoluteSeek, 0, 0)) :: IO (Either IOError ()))
535 6fd8ceff Klaus Aehlig
536 6fd8ceff Klaus Aehlig
-- | File stat identifier.
537 6fd8ceff Klaus Aehlig
type FStat = (EpochTime, FileID, FileOffset)
538 6fd8ceff Klaus Aehlig
539 6fd8ceff Klaus Aehlig
-- | Null 'FStat' value.
540 6fd8ceff Klaus Aehlig
nullFStat :: FStat
541 6fd8ceff Klaus Aehlig
nullFStat = (-1, -1, -1)
542 6fd8ceff Klaus Aehlig
543 6fd8ceff Klaus Aehlig
-- | Computes the file cache data from a FileStatus structure.
544 6fd8ceff Klaus Aehlig
buildFileStatus :: FileStatus -> FStat
545 6fd8ceff Klaus Aehlig
buildFileStatus ofs =
546 6fd8ceff Klaus Aehlig
    let modt = modificationTime ofs
547 6fd8ceff Klaus Aehlig
        inum = fileID ofs
548 6fd8ceff Klaus Aehlig
        fsize = fileSize ofs
549 6fd8ceff Klaus Aehlig
    in (modt, inum, fsize)
550 6fd8ceff Klaus Aehlig
551 6fd8ceff Klaus Aehlig
-- | Wrapper over 'buildFileStatus'. This reads the data from the
552 6fd8ceff Klaus Aehlig
-- filesystem and then builds our cache structure.
553 6fd8ceff Klaus Aehlig
getFStat :: FilePath -> IO FStat
554 6fd8ceff Klaus Aehlig
getFStat p = liftM buildFileStatus (getFileStatus p)
555 6fd8ceff Klaus Aehlig
556 6fd8ceff Klaus Aehlig
-- | Check if the file needs reloading
557 6fd8ceff Klaus Aehlig
needsReload :: FStat -> FilePath -> IO (Maybe FStat)
558 6fd8ceff Klaus Aehlig
needsReload oldstat path = do
559 6fd8ceff Klaus Aehlig
  newstat <- getFStat path
560 6fd8ceff Klaus Aehlig
  return $ if newstat /= oldstat
561 6fd8ceff Klaus Aehlig
             then Just newstat
562 6fd8ceff Klaus Aehlig
             else Nothing