-- | -- Module : System.AtomicWrite.Internal -- Copyright : © 2015-2019 Stack Builders Inc. -- License : MIT -- -- Maintainer : Stack Builders -- Stability : experimental -- Portability : portable -- -- Provides functionality to create a temporary file with correct permissions -- atomically. module System.AtomicWrite.Internal where import System.Directory (doesFileExist, renameFile) import System.FilePath (takeDirectory) import System.IO (Handle, hClose, hSetBinaryMode, openTempFile, openTempFileWithDefaultPermissions) import System.Posix.Types (FileMode) import System.PosixCompat.Files (fileMode, getFileStatus, setFileMode) -- | Returns a temporary file with permissions correctly set. Chooses -- either previously-set permissions if the file that we're writing -- to existed, or permissions following the current umask. tempFileFor :: FilePath -- ^ The target filepath that we will replace atomically. -> IO (FilePath, Handle) tempFileFor targetFilePath = doesFileExist targetFilePath >>= tmpFile targetFilePath (takeDirectory targetFilePath) "atomic.write" where tmpFile :: FilePath -> FilePath -> String -> Bool -> IO (FilePath, Handle) tmpFile targetPath workingDirectory template previousExisted = if previousExisted then openTempFile workingDirectory template >>= \(tmpPath, handle) -> getFileStatus targetPath >>= setFileMode tmpPath . fileMode >> return (tmpPath, handle) else openTempFileWithDefaultPermissions workingDirectory template closeAndRename :: Handle -> FilePath -> FilePath -> IO () closeAndRename tmpHandle tempFile destFile = hClose tmpHandle >> renameFile tempFile destFile maybeSetFileMode :: FilePath -> Maybe FileMode -> IO () maybeSetFileMode path = maybe ( return () ) ( \mode -> setFileMode path mode ) -- Helper Function atomicWriteFileMaybeModeText :: Maybe FileMode -- ^ The mode to set the file to -> FilePath -- ^ The path where the file will be updated or created -> (Handle -> a -> IO ()) -- ^ The function to use to write on the file -> a -- ^ The content to write to the file -> IO () atomicWriteFileMaybeModeText mmode path hF text = tempFileFor path >>= \(tmpPath, h) -> hSetBinaryMode h False >> hF h text >> closeAndRename h tmpPath path >> maybeSetFileMode path mmode -- Helper Function atomicWriteFileMaybeModeBinary :: Maybe FileMode -- ^ The mode to set the file to -> FilePath -- ^ The path where the file will be updated or created -> (Handle -> a -> IO ()) -- ^ The function to use to write on the file -> a -- ^ The content to write to the file -> IO () atomicWriteFileMaybeModeBinary mmode path hF text = tempFileFor path >>= \(tmpPath, h) -> hSetBinaryMode h True >> hF h text >> closeAndRename h tmpPath path >> maybeSetFileMode path mmode