Skip to content

Commit 41cc0ef

Browse files
committed
Create output file atomically
1 parent 01990bd commit 41cc0ef

File tree

2 files changed

+39
-9
lines changed

2 files changed

+39
-9
lines changed

alex.cabal

+1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ executable alex
105105
, array
106106
, containers
107107
, directory
108+
, filepath
108109

109110
default-language: Haskell98
110111
default-extensions: CPP

src/Main.hs

+38-9
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,20 @@ import Paths_alex ( version, getDataDir )
2828
import Control.Exception as Exception ( block, unblock, catch, throw )
2929
#endif
3030
#if __GLASGOW_HASKELL__ >= 610
31-
import Control.Exception ( bracketOnError )
31+
import Control.Exception ( bracketOnError, handleJust )
3232
#endif
33-
import Control.Monad ( when, liftM )
33+
import System.IO.Error (isDoesNotExistError)
34+
import Control.Monad ( guard, when, liftM )
3435
import Data.Char ( chr )
3536
import Data.List ( isSuffixOf, nub )
3637
import Data.Version ( showVersion )
3738
import System.Console.GetOpt ( getOpt, usageInfo, ArgOrder(..), OptDescr(..), ArgDescr(..) )
38-
import System.Directory ( removeFile )
39+
import System.Directory ( renameFile, removeFile )
3940
import System.Environment ( getProgName, getArgs )
41+
import System.FilePath (splitFileName)
4042
import System.Exit ( ExitCode(..), exitWith )
4143
import System.IO ( stderr, Handle, IOMode(..), openFile, hClose, hPutStr, hPutStrLn )
44+
import qualified System.IO as IO ( openTempFileWithDefaultPermissions )
4245
#if __GLASGOW_HASKELL__ >= 612
4346
import System.IO ( hGetContents, hSetEncoding, utf8 )
4447
#endif
@@ -66,6 +69,37 @@ alexOpenFile file mode = do
6669
alexOpenFile = openFile
6770
#endif
6871

72+
openTempFileWithDefaultPermissions :: FilePath -> String -> IO (FilePath, Handle)
73+
#if __GLASGOW_HASKELL__ >= 612
74+
openTempFileWithDefaultPermissions dir template = do
75+
(file, h) <- IO.openTempFileWithDefaultPermissions dir template
76+
hSetEncoding h utf8
77+
return (file, h)
78+
#else
79+
openTempFileWithDefaultPermissions = IO.openTempFileWithDefaultPermissions
80+
#endif
81+
82+
tryRemoveFile :: FilePath -> IO ()
83+
tryRemoveFile name = handleJust (guard . isDoesNotExistError) return (removeFile name)
84+
85+
createAtomically :: FilePath -> (Handle -> IO a) -> IO a
86+
createAtomically o_file action = bracketOnError open cleanup $ \ (o_file_tmp, h) -> do
87+
r <- action h
88+
hClose h
89+
renameFile o_file_tmp o_file
90+
return r
91+
where
92+
open :: IO (FilePath, Handle)
93+
open = openTempFileWithDefaultPermissions dir template
94+
where
95+
(dir, file) = splitFileName o_file
96+
template = file ++ "~"
97+
98+
cleanup :: (FilePath, Handle) -> IO ()
99+
cleanup (o_file_tmp, h) = do
100+
hClose h
101+
tryRemoveFile o_file_tmp
102+
69103
-- `main' decodes the command line arguments and calls `alex'.
70104

71105
main:: IO ()
@@ -169,11 +203,7 @@ alex cli file basename script = do
169203

170204
scheme <- getScheme directives
171205

172-
-- open the output file; remove it if we encounter an error
173-
bracketOnError
174-
(alexOpenFile o_file WriteMode)
175-
(\h -> do hClose h; removeFile o_file)
176-
$ \out_h -> do
206+
createAtomically o_file $ \ out_h -> do
177207

178208
let scanner2, scanner_final :: Scanner
179209
scs :: [StartCode]
@@ -242,7 +272,6 @@ alex cli file basename script = do
242272
tmplt <- alexReadFile $ template_dir ++ "/AlexTemplate.hs"
243273
hPutStr out_h tmplt
244274

245-
hClose out_h
246275
finish_info
247276

248277
getScheme :: [Directive] -> IO Scheme

0 commit comments

Comments
 (0)