Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / Utils.hs @ c92b4671

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