ajhc-0.8.0.1: src/Support/TempDir.hs
{-# LANGUAGE ForeignFunctionInterface,ViewPatterns,RecordWildCards #-}
-- Various routines for dealing with temporary directories and files.
module Support.TempDir(
getTempDir,
createTempFile,
fileInTempDir,
cleanTempDir,
setTempDir,
addAtExit,
withStackStatus,
wrapMain
) where
import Control.Exception as E
import Control.Monad
import Data.IORef
import Data.Maybe
import System.Directory
import System.Exit
import System.FilePath as FP
import System.IO
import System.IO.Unsafe
import System.IO.Temp
import Text.Printf
import qualified Data.Set as Set
import GenUtil (iocatch)
import Support.CompatMingw32
data TempDir = TempDir {
tempDirClean :: Bool, -- ^ whether to delete the directory afterwords.
tempDirDump :: Bool,
tempDirPath :: Maybe String,
tempDirAtExit :: [IO ()],
tempDirCleanup :: Set.Set FilePath
}
putLog :: String -> IO ()
--putLog = putStrLn
putLog _ = return ()
cleanTempDir :: Bool -> IO ()
cleanTempDir b = modifyIORef tdRef $ \x -> x { tempDirClean = b }
setTempDir :: FilePath -> IO ()
setTempDir (FP.normalise -> fp) = do
TempDir {..} <- readIORef tdRef
when (isJust $ tempDirPath) $ do
fail $ printf
"Cannot set temp directory to '%s'; it is already set to '%s'."
fp (fromJust tempDirPath)
putLog $ printf "Setting work directory to '%s'" fp
createDirectoryIfMissing False fp
writeIORef tdRef TempDir { tempDirPath = Just fp, .. }
cleanTempDir False
getTempDir :: IO FilePath
getTempDir = do
td <- readIORef tdRef
case tempDirPath td of
Just fp -> return fp
Nothing -> do
tmpdir <- getTemporaryDirectory
fp <- createTempDirectory tmpdir "jhc_"
putLog $ printf "Created work directory '%s'" fp
writeIORef tdRef td { tempDirPath = Just fp }
return fp
addAtExit :: IO () -> IO ()
addAtExit action = do
td <- readIORef tdRef
writeIORef tdRef td { tempDirAtExit = action:tempDirAtExit td }
createTempFile :: FilePath -> IO (FilePath, Handle)
createTempFile (FP.normalise -> fp) = do
unless (filePathSafe fp) $
fail $ "createTempFile: unsafe path " ++ fp
dir <- getTempDir
(fp,h) <- openBinaryTempFile dir (if null fp then "temp.tmp" else fp)
putLog $ printf "Created temporary file '%s'" fp
addCleanup fp
return (fp,h)
-- make sure nothing is sneaky about the file path
filePathSafe fp = FP.isRelative fp &&
".." `notElem` FP.splitPath fp && not (hasDrive fp)
fileInTempDir :: FilePath -> (FilePath -> IO ()) -> IO FilePath
fileInTempDir (FP.normalise -> fp) action = do
unless (filePathSafe fp) $
fail $ "fileinTempDir: unsafe path " ++ fp
let (FP.normalise -> dpart,_) = FP.splitFileName fp
tdir <- getTempDir
let f ("./":ps) cp = f ps cp
f (".":ps) cp = f ps cp
f (p:ps) cp = do
putLog $ printf "Creating directory '%s' '%s' '%s' '%s' '%s'" tdir cp p dpart fp
createDirectoryIfMissing False (tdir </> cp </> p)
let cp' = FP.normalise (cp </> p)
addCleanup cp'
f ps cp'
f [] _ = return ()
f (FP.splitPath dpart) ""
--unless (null $ FP.normalise dpart) $
-- fold (FP.splitPath dpart) $ addCleanup
-- createDirectoryIfMissing True (tdir </> dpart)
let nfp = FP.normalise (tdir </> fp)
b <- addCleanup fp
when b $ action nfp
return $ noEscapePath nfp
cleanUp :: IO ()
cleanUp = do
td <- readIORef tdRef
sequence_ (tempDirAtExit td)
if not (tempDirClean td) ||
isNothing (tempDirPath td) then return () else do
dir <- getTempDir
forM_ (reverse . Set.toList $ tempDirCleanup td) $ \fp -> do
putLog $ printf "Removing '%s'" (dir </> fp)
ignoreError (removeDirectory $ dir </> fp)
ignoreError (removeFile $ dir </> fp)
putLog $ printf "Removing '%s'" dir
ignoreError (removeDirectory dir)
addCleanup :: FilePath -> IO Bool
addCleanup fp = do
td <- readIORef tdRef
if fp `Set.member` tempDirCleanup td then return False else do
writeIORef tdRef td { tempDirCleanup = fp `Set.insert` tempDirCleanup td }
return True
wrapMain :: IO () -> IO ()
wrapMain main = E.catch (main >> cleanUp) f where
panic = raiseSigIntCompat
f (fromException -> Just code) = cleanUp >> exitWith code
f (fromException -> Just UserInterrupt) = cleanUp >> panic
f e = do
ss <- readIORef stackRef
td <- readIORef tdRef
case tempDirPath td of
Just td -> hPutStrLn stderr $
printf "Exiting abnormally. Work directory is '%s'" td
_ -> return ()
unless (null ss) $
forM_ ("Stack:":ss) (hPutStrLn stderr)
throwIO e
-------------------
-- support routines
-------------------
ignoreError :: IO () -> IO ()
ignoreError action = iocatch action (\_ -> return ())
{-# NOINLINE tdRef #-}
tdRef :: IORef TempDir
tdRef = unsafePerformIO $ newIORef TempDir {
tempDirClean = True,
tempDirDump = False,
tempDirPath = Nothing,
tempDirAtExit = [],
tempDirCleanup = Set.empty
}
{-# NOINLINE stackRef #-}
stackRef :: IORef [String]
stackRef = unsafePerformIO $ newIORef []
withStackStatus :: String -> IO a -> IO a
withStackStatus s action = do
cs <- readIORef stackRef
writeIORef stackRef (s:cs)
r <- action
writeIORef stackRef cs
return r