Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / Utils.hs @ 69bf84e1

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