Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / Utils.hs @ 9a8952e0

History | View | Annotate | Download (25.2 kB)

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