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