#2298: renameFile is not atomic on Windows
-----------------------+----------------------------------------------------
Reporter: duncan | Owner:
Type: bug | Status: new
Priority: normal | Component: libraries/base
Version: 6.8.2 | Severity: normal
Keywords: | Testcase:
Architecture: x86 | Os: Windows
-----------------------+----------------------------------------------------
The Haskell 98 spec says about `renameFile`
Computation `renameFile old new` changes the name of an existing file
system object from old to new. If the new object already exists, it is
atomically replaced by the old object.
The `renameFile` function has been tricky on Windows historically because
Windows 9x did not directly support overwriting an existing file. Windows
NT and later support [http://msdn.microsoft.com/en-
us/library/aa365240(VS.85).aspx `MoveFileEx`] which has a flag
`MOVEFILE_REPLACE_EXISTING`.
The current `__hscore_renameFile` implementation for Windows uses a
complex range of tests and workarounds to allow overwriting of existing
files. Of course this cannot be atomic. For Windows NT it does use
`MoveFileEx()` but then if that fails it goes and tries the old tricks
anyway! So abandoning all promises of atomicity it goes and tries to
delete the target file and then a final attempt to rename over the now-
deleted target file.
As far as I can see this is bonkers. It should use `MoveFileEx()` exactly
once and if that fails then the whole thing fails. That way we preserve
any atomicity guarantees that `MoveFileEx()` might provide. Note that the
MSDN documentation doesn't actually say if the rename is atomic, even in
the case of two files in the same directory.
With a `renameFile` that ''does'' meet the H98 requirement we can write an
`writeFileAtomic` function that either succeeds and replaces the target
file or fails without altering the target file in any way. Additionally,
other threads or processes will not see any intermediate states of the
target file (though they would see the temp file created in the same
directory). This is how text editors etc implement reliable file writing.
Glib has a function like this [http://library.gnome.org/devel/glib/stable
/glib-File-Utilities.html#g-file-set-contents g-file-set-contents].
{{{
writeFileAtomic :: FilePath -> String -> IO ()
writeFileAtomic targetFile content =
Exception.bracketOnError
(openTempFile targetDir template)
(\(tmpFile, tmpHandle) -> hClose tmpHandle
>> removeFile tmpFile)
(\(tmpFile, tmpHandle) -> hPutStr tmpHandle content
>> hClose tmpHandle
>> renameFile tmpFile targetFile)
where
template = targetName <.> "tmp"
(targetDir,targetName) = splitFileName targetFile
}}}
Indeed it's not impossible to imagine using this or something similar for
the actual H98 `writeFile` implementation.
--
Ticket URL: <http://hackage.haskell.org/trac/ghc/ticket/2298>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler_______________________________________________
Glasgow-haskell-bugs mailing list
[email protected]
http://www.haskell.org/mailman/listinfo/glasgow-haskell-bugs