Revision 5f6515b6

b/Makefile.am
135 135
	src/Ganeti/Storage/Drbd \
136 136
	src/Ganeti/Storage/Lvm \
137 137
	src/Ganeti/THH \
138
	src/Ganeti/Utils \
138 139
	src/Ganeti/WConfd \
139 140
	test/hs \
140 141
	test/hs/Test \
......
803 804
	src/Ganeti/Types.hs \
804 805
	src/Ganeti/UDSServer.hs \
805 806
	src/Ganeti/Utils.hs \
807
	src/Ganeti/Utils/Atomic.hs \
806 808
	src/Ganeti/VCluster.hs \
807 809
	src/Ganeti/WConfd/ConfigState.hs \
808 810
	src/Ganeti/WConfd/Core.hs \
b/src/Ganeti/JQueue.hs
103 103
import Ganeti.THH
104 104
import Ganeti.Types
105 105
import Ganeti.Utils
106
import Ganeti.Utils.Atomic
106 107
import Ganeti.VCluster (makeVirtualPath)
107 108

  
108 109
-- * Data types
b/src/Ganeti/Utils.hs
65 65
  , setOwnerAndGroupFromNames
66 66
  , setOwnerWGroupR
67 67
  , formatOrdinal
68
  , atomicWriteFile
69
  , atomicUpdateFile
70
  , atomicUpdateLockedFile
71
  , atomicUpdateLockedFile_
72 68
  , tryAndLogIOError
73 69
  , lockFile
74
  , withLockedFile
75 70
  , FStat
76 71
  , nullFStat
77 72
  , getFStat
......
85 80

  
86 81
import Control.Concurrent
87 82
import Control.Exception (try)
88
import qualified Control.Exception.Lifted as L
89 83
import Control.Monad
90
import Control.Monad.Base (MonadBase(..))
91 84
import Control.Monad.Error
92
import Control.Monad.Trans.Control
93 85
import Data.Char (toUpper, isAlphaNum, isDigit, isSpace)
94 86
import qualified Data.Either as E
95 87
import Data.Function (on)
......
98 90
import qualified Data.Map as M
99 91
import Numeric (showOct)
100 92
import System.Directory (renameFile, createDirectoryIfMissing)
101
import System.FilePath.Posix (takeDirectory, takeBaseName)
93
import System.FilePath.Posix (takeDirectory)
102 94
import System.INotify
103 95
import System.Posix.Types
104 96

  
......
559 551
  where tens     = num `mod` 10
560 552
        suffix s = show num ++ s
561 553

  
562
-- | Atomically write a file, by first writing the contents into a temporary
563
-- file and then renaming it to the old position.
564
atomicWriteFile :: FilePath -> String -> IO ()
565
atomicWriteFile path contents = atomicUpdateFile path
566
                                  (\_ fh -> hPutStr fh contents)
567

  
568
-- | Atomically update a file, by first creating a temporary file, running the
569
-- given action on it, and then renaming it to the old position.
570
-- Usually the action will write to the file and update its permissions.
571
-- The action is allowed to close the file descriptor, but isn't required to do
572
-- so.
573
atomicUpdateFile :: (MonadBaseControl IO m)
574
                 => FilePath -> (FilePath -> Handle -> m a) -> m a
575
atomicUpdateFile path action = do
576
  (tmppath, tmphandle) <- liftBase $ openTempFile (takeDirectory path)
577
                                                  (takeBaseName path)
578
  r <- L.finally (action tmppath tmphandle) (liftBase $ hClose tmphandle)
579
  -- if all went well, rename the file
580
  liftBase $ renameFile tmppath path
581
  return r
582

  
583
-- | Opens a file in a R/W mode, locks it (blocking if needed) and runs
584
-- a given action while the file is locked. Releases the lock and
585
-- closes the file afterwards.
586
withLockedFile :: (MonadError e m, Error e, MonadBaseControl IO m)
587
               => FilePath -> (Fd -> m a) -> m a
588
withLockedFile path =
589
    L.bracket (openAndLock path) (liftBase . closeFd)
590
  where
591
    openAndLock :: (MonadError e m, Error e, MonadBaseControl IO m)
592
                => FilePath -> m Fd
593
    openAndLock p = liftBase $ do
594
      fd <- openFd p ReadWrite Nothing defaultFileFlags
595
      waitToSetLock fd (WriteLock, AbsoluteSeek, 0, 0)
596
      return fd
597

  
598
-- | Just as 'atomicUpdateFile', but in addition locks the file during the
599
-- operation using 'withLockedFile' and checks if the file has been modified.
600
-- The action is only run if it hasn't, otherwise an error is thrown.
601
-- The file must exist.
602
-- Returns the new file status after the operation is finished.
603
atomicUpdateLockedFile :: FilePath
604
                       -> FStat
605
                       -> (FilePath -> Handle -> IO a)
606
                       -> ResultT IOError IO (FStat, a)
607
atomicUpdateLockedFile path fstat action =
608
    withLockedFile path checkStatAndRun
609
  where
610
    checkStatAndRun _ = do
611
      newstat <- liftIO $ getFStat path
612
      unless (fstat == newstat)
613
             (failError $ "Cannot overwrite file " ++ path ++
614
                          ": it has been modified since last written" ++
615
                          " (" ++ show fstat ++ " != " ++ show newstat ++ ")")
616
      liftIO $ atomicUpdateFile path actionAndStat
617
    actionAndStat tmppath tmphandle = do
618
      r <- action tmppath tmphandle
619
      hClose tmphandle -- close the handle so that we get meaningful stats
620
      finalstat <- liftIO $ getFStat tmppath
621
      return (finalstat, r)
622

  
623
-- | Just as 'atomicUpdateLockedFile', but discards the action result.
624
atomicUpdateLockedFile_ :: FilePath
625
                        -> FStat
626
                        -> (FilePath -> Handle -> IO a)
627
                        -> ResultT IOError IO FStat
628
atomicUpdateLockedFile_ path oldstat
629
  = liftM fst . atomicUpdateLockedFile path oldstat
630

  
631 554
-- | Attempt, in a non-blocking way, to obtain a lock on a given file; report
632 555
-- back success.
633 556
lockFile :: FilePath -> IO (Result ())
b/src/Ganeti/Utils/Atomic.hs
1
{-# LANGUAGE FlexibleContexts #-}
2

  
3
{-| Utility functions for atomic file access. -}
4

  
5
{-
6

  
7
Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
8

  
9
This program is free software; you can redistribute it and/or modify
10
it under the terms of the GNU General Public License as published by
11
the Free Software Foundation; either version 2 of the License, or
12
(at your option) any later version.
13

  
14
This program is distributed in the hope that it will be useful, but
15
WITHOUT ANY WARRANTY; without even the implied warranty of
16
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17
General Public License for more details.
18

  
19
You should have received a copy of the GNU General Public License
20
along with this program; if not, write to the Free Software
21
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22
02110-1301, USA.
23

  
24
-}
25

  
26
module Ganeti.Utils.Atomic
27
  ( atomicWriteFile
28
  , atomicUpdateFile
29
  , withLockedFile
30
  , atomicUpdateLockedFile
31
  , atomicUpdateLockedFile_
32
  ) where
33

  
34
import qualified Control.Exception.Lifted as L
35
import Control.Monad
36
import Control.Monad.Base (MonadBase(..))
37
import Control.Monad.Error
38
import Control.Monad.Trans.Control
39
import System.FilePath.Posix (takeDirectory, takeBaseName)
40
import System.IO
41
import System.Directory (renameFile)
42
import System.Posix.IO
43
import System.Posix.Types
44

  
45
import Ganeti.BasicTypes
46
import Ganeti.Errors
47
import Ganeti.Utils
48

  
49
-- | Atomically write a file, by first writing the contents into a temporary
50
-- file and then renaming it to the old position.
51
atomicWriteFile :: FilePath -> String -> IO ()
52
atomicWriteFile path contents = atomicUpdateFile path
53
                                  (\_ fh -> hPutStr fh contents)
54

  
55
-- | Atomically update a file, by first creating a temporary file, running the
56
-- given action on it, and then renaming it to the old position.
57
-- Usually the action will write to the file and update its permissions.
58
-- The action is allowed to close the file descriptor, but isn't required to do
59
-- so.
60
atomicUpdateFile :: (MonadBaseControl IO m)
61
                 => FilePath -> (FilePath -> Handle -> m a) -> m a
62
atomicUpdateFile path action = do
63
  (tmppath, tmphandle) <- liftBase $ openTempFile (takeDirectory path)
64
                                                  (takeBaseName path)
65
  r <- L.finally (action tmppath tmphandle) (liftBase $ hClose tmphandle)
66
  -- if all went well, rename the file
67
  liftBase $ renameFile tmppath path
68
  return r
69

  
70
-- | Opens a file in a R/W mode, locks it (blocking if needed) and runs
71
-- a given action while the file is locked. Releases the lock and
72
-- closes the file afterwards.
73
withLockedFile :: (MonadError e m, Error e, MonadBaseControl IO m)
74
               => FilePath -> (Fd -> m a) -> m a
75
withLockedFile path =
76
    L.bracket (openAndLock path) (liftBase . closeFd)
77
  where
78
    openAndLock :: (MonadError e m, Error e, MonadBaseControl IO m)
79
                => FilePath -> m Fd
80
    openAndLock p = liftBase $ do
81
      fd <- openFd p ReadWrite Nothing defaultFileFlags
82
      waitToSetLock fd (WriteLock, AbsoluteSeek, 0, 0)
83
      return fd
84

  
85
-- | Just as 'atomicUpdateFile', but in addition locks the file during the
86
-- operation using 'withLockedFile' and checks if the file has been modified.
87
-- The action is only run if it hasn't, otherwise an error is thrown.
88
-- The file must exist.
89
-- Returns the new file status after the operation is finished.
90
atomicUpdateLockedFile :: FilePath
91
                       -> FStat
92
                       -> (FilePath -> Handle -> IO a)
93
                       -> ResultG (FStat, a)
94
atomicUpdateLockedFile path fstat action =
95
    toErrorBase . withErrorT (LockError . (show :: IOError -> String))
96
    $ withLockedFile path checkStatAndRun
97
  where
98
    checkStatAndRun _ = do
99
      newstat <- liftIO $ getFStat path
100
      unless (fstat == newstat)
101
             (failError $ "Cannot overwrite file " ++ path ++
102
                          ": it has been modified since last written" ++
103
                          " (" ++ show fstat ++ " != " ++ show newstat ++ ")")
104
      liftIO $ atomicUpdateFile path actionAndStat
105
    actionAndStat tmppath tmphandle = do
106
      r <- action tmppath tmphandle
107
      hClose tmphandle -- close the handle so that we get meaningful stats
108
      finalstat <- liftIO $ getFStat tmppath
109
      return (finalstat, r)
110

  
111
-- | Just as 'atomicUpdateLockedFile', but discards the action result.
112
atomicUpdateLockedFile_ :: FilePath
113
                        -> FStat
114
                        -> (FilePath -> Handle -> IO a)
115
                        -> ResultG FStat
116
atomicUpdateLockedFile_ path oldstat
117
  = liftM fst . atomicUpdateLockedFile path oldstat

Also available in: Unified diff