module System.Unix.Directory
( find
, removeRecursiveSafely
, unmountRecursiveSafely
, renameFileWithBackup
, withWorkingDirectory
, withTemporaryDirectory
, mkdtemp
)
where
import Control.Exception
import Data.List (isSuffixOf)
import System.Process
import System.Directory
import System.Exit
import System.FilePath
import System.IO
import System.Posix.Files
import System.Posix.Types
import Foreign.C
find :: FilePath -> IO [(FilePath, FileStatus)]
find path =
do
status <- getSymbolicLinkStatus path
case isDirectory status of
True ->
do
subs <- getDirectoryContents path >>=
return . map (path </>) . filter (not . flip elem [".", ".."]) >>=
mapM find >>=
return . concat
return $ (path, status) : subs
False ->
return [(path, status)]
traverse :: FilePath -> (FilePath -> IO ()) -> (FilePath -> IO ()) -> (FilePath -> IO ()) -> IO ()
traverse path f d m =
do
result <- try $ getSymbolicLinkStatus path
either (\ (_ :: SomeException) -> return ()) (doPath path) result
where
doPath path status =
if isDirectory status then
do
getDirectoryContents path >>= mapM (doDirectoryFile 1 status path)
d path else
f path
doDirectoryFile :: Int -> FileStatus -> FilePath -> String -> IO ()
doDirectoryFile _ _ _ "." = return ()
doDirectoryFile _ _ _ ".." = return ()
doDirectoryFile tries _ _ _ | tries >= 5 =
error ("Couldn't unmount file system on " ++ path)
doDirectoryFile tries status path name =
do
let child = path </> name
childStatus <- getSymbolicLinkStatus child
if deviceID status == deviceID childStatus then
doPath child childStatus else
do
if tries > 1 then hPutStrLn stderr ("try " ++ show tries ++ ":") else return ()
m child
doDirectoryFile (tries + 1) status path name
removeRecursiveSafely :: FilePath -> IO ()
removeRecursiveSafely path =
System.Unix.Directory.traverse path removeFile removeDirectory umount
where
umount path =
do
hPutStrLn stderr ("-- removeRecursiveSafely: unmounting " ++ path)
let cmd = "umount -l " ++ path
result <- system cmd
case result of
ExitSuccess -> return ()
ExitFailure n -> error ("Failure: " ++ cmd ++ " -> " ++ show n)
unmountRecursiveSafely :: FilePath -> IO ()
unmountRecursiveSafely path =
System.Unix.Directory.traverse path noOp noOp umount
where
noOp _ = return ()
umount path =
do
hPutStrLn stderr ("-- unmountRecursiveSafely: unmounting " ++ path)
let cmd = "umount -l " ++ path
code <- system cmd
case code of
ExitSuccess -> return ()
ExitFailure n -> error ("Failure: " ++ cmd ++ " -> " ++ show n)
renameFileWithBackup :: FilePath -> FilePath -> IO ()
renameFileWithBackup src dst =
do
removeIfExists (dst ++ "~")
renameIfExists dst (dst ++ "~")
System.Directory.renameFile src dst
where
removeIfExists path =
do exists <- doesFileExist path
if exists then removeFile path else return ()
renameIfExists src dst =
do exists <- doesFileExist src
if exists then System.Directory.renameFile src dst else return ()
withWorkingDirectory :: FilePath -> IO a -> IO a
withWorkingDirectory dir action =
bracket getCurrentDirectory setCurrentDirectory (\ _ -> setCurrentDirectory dir >> action)
withTemporaryDirectory :: FilePath -> (FilePath -> IO a) -> IO a
withTemporaryDirectory fp f =
do sysTmpDir <- getTemporaryDirectory
bracket (mkdtemp (sysTmpDir </> fp))
removeRecursiveSafely
f
foreign import ccall unsafe "stdlib.h mkdtemp"
c_mkdtemp :: CString -> IO CString
mkdtemp :: FilePath -> IO FilePath
mkdtemp template =
withCString (if "XXXXXX" `isSuffixOf` template then template else (template ++ "XXXXXX")) $ \ ptr -> do
cname <- throwErrnoIfNull "mkdtemp" (c_mkdtemp ptr)
name <- peekCString cname
return name