From d9f367f611a2d9fc00a965ffe247841c33c73d62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCtke-Stetzkamp?= Date: Sat, 2 May 2020 11:00:39 +0200 Subject: [PATCH] Implementation of durable file rename --- unliftio/src/UnliftIO/IO/File.hs | 25 +++++++++++++++++++++++++ unliftio/src/UnliftIO/IO/File/Posix.hs | 12 ++++++++++++ 2 files changed, 37 insertions(+) diff --git a/unliftio/src/UnliftIO/IO/File.hs b/unliftio/src/UnliftIO/IO/File.hs index 192f523..fd5960a 100644 --- a/unliftio/src/UnliftIO/IO/File.hs +++ b/unliftio/src/UnliftIO/IO/File.hs @@ -82,12 +82,14 @@ module UnliftIO.IO.File , withBinaryFileDurable , withBinaryFileDurableAtomic , ensureFileDurable + , renameFileDurable ) where import Data.ByteString as B (ByteString, writeFile) import Control.Monad.IO.Unlift import UnliftIO.IO (Handle, IOMode(..), withBinaryFile) +import UnliftIO.Directory (renameFile) #if WINDOWS @@ -102,6 +104,8 @@ withBinaryFileDurable = withBinaryFile withBinaryFileDurableAtomic = withBinaryFile withBinaryFileAtomic = withBinaryFile +renameFileDurable = renameFile + #else import qualified Data.ByteString as B (hPut) @@ -119,8 +123,29 @@ writeBinaryFileAtomic fp bytes = withBinaryFileDurable = Posix.withBinaryFileDurable withBinaryFileDurableAtomic = Posix.withBinaryFileDurableAtomic withBinaryFileAtomic = Posix.withBinaryFileAtomic + +renameFileDurable = Posix.renameFileDurable #endif +-- | When a file is renamed, it is necessary to execute @fsync()@ on the +-- directory that contains the file now and afterwards on the directory where +-- the file was before, so that the rename is durable. +-- +-- Remark: This is also atomic if both locations of the file are on the same +-- filesystem. However, it could happen that the operation leads to data loss, +-- if a crash happens after the rename and before the first fsync finishes. This +-- is because on an async filesystem the write of the old directory might +-- already written to disk and the change on the new directory is not. It the +-- function call returns, the change is durable. Nevertheless, this will not +-- happen on filesystems using journaling, that is, allmost modern filesystems. +-- +-- === Cross-Platform support +-- +-- This function is a noop on Windows platforms. +-- +renameFileDurable :: MonadIO m => FilePath -> FilePath -> m () +-- Implementation is at the top of the module + -- | After a file is closed, this function opens it again and executes @fsync()@ -- internally on both the file and the directory that contains it. Note that this function -- is intended to work around the non-durability of existing file APIs, as opposed to diff --git a/unliftio/src/UnliftIO/IO/File/Posix.hs b/unliftio/src/UnliftIO/IO/File/Posix.hs index ed0008a..cbce87a 100644 --- a/unliftio/src/UnliftIO/IO/File/Posix.hs +++ b/unliftio/src/UnliftIO/IO/File/Posix.hs @@ -9,6 +9,7 @@ module UnliftIO.IO.File.Posix , withBinaryFileDurableAtomic , withBinaryFileAtomic , ensureFileDurable + , renameFileDurable ) where @@ -39,6 +40,7 @@ import System.IO.Error (ioeGetErrorType, isAlreadyExistsError, import qualified System.Posix.Files as Posix import System.Posix.Internals (CFilePath, c_close, c_safe_open, withFilePath) import System.Posix.Types (CMode(..), Fd(..), FileMode) +import UnliftIO.Directory (renameFile) import UnliftIO.Exception import UnliftIO.IO import UnliftIO.MVar @@ -580,3 +582,13 @@ withBinaryFileAtomic filePath iomode action = liftIO $ atomicTempFileRename Nothing mFileMode eTmpFile filePath pure res + +-- | See `renameFileDurable` +renameFileDurable :: + MonadIO m => FilePath -> FilePath -> m () +renameFileDurable oldName newName = + liftIO $ withDirectory (takeDirectory oldName) $ \oldDirFd -> + withDirectory (takeDirectory newName) $ \newDirFd -> do + renameFile oldName newName + fsyncDirectoryFd "renameFileDurable" newDirFd + fsyncDirectoryFd "renameFileDurable" oldDirFd