#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

Reply via email to