{-# LANGUAGE CApiFFI #-} {-# LANGUAGE CPP #-} {-# LANGUAGE ForeignFunctionInterface #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ViewPatterns #-} {-# OPTIONS_GHC -Wall #-} module GHCup.Prelude.File.Posix.Traversals ( -- lower-level stuff readDirEnt , readDirEntPortable , openDirStreamPortable , closeDirStreamPortable , unpackDirStream , DirStreamPortable ) where #include #include #include #include #include #include #if __GLASGOW_HASKELL__ < 710 import Control.Applicative ((<$>)) #endif import GHCup.Prelude.File.Posix.Foreign import Unsafe.Coerce (unsafeCoerce) import Foreign.C.Error import Foreign.C.String import Foreign.C.Types import Foreign.Ptr import Foreign.Storable import System.Posix import Foreign (alloca) import System.Posix.Internals (peekFilePath) import System.FilePath ---------------------------------------------------------- -- dodgy stuff data {-# CTYPE "DIR" #-} CDir data {-# CTYPE "struct dirent" #-} CDirent -- Posix doesn't export DirStream, so to re-use that type we need to use -- unsafeCoerce. It's just a newtype, so this is a legitimate usage. -- ugly trick. unpackDirStream :: DirStream -> Ptr CDir unpackDirStream = unsafeCoerce -- the __hscore_* functions are defined in the unix package. We can import them and let -- the linker figure it out. foreign import ccall unsafe "__hscore_readdir" c_readdir :: Ptr CDir -> Ptr (Ptr CDirent) -> IO CInt foreign import ccall unsafe "__hscore_free_dirent" c_freeDirEnt :: Ptr CDirent -> IO () foreign import ccall unsafe "__hscore_d_name" c_name :: Ptr CDirent -> IO CString foreign import capi unsafe "dirutils.h __posixdir_d_type" c_type :: Ptr CDirent -> IO DirType ---------------------------------------------------------- -- less dodgy but still lower-level readDirEnt :: DirStream -> IO (DirType, FilePath) readDirEnt (unpackDirStream -> dirp) = alloca $ \ptr_dEnt -> loop ptr_dEnt where loop ptr_dEnt = do resetErrno r <- c_readdir dirp ptr_dEnt if r == 0 then do dEnt <- peek ptr_dEnt if dEnt == nullPtr then return (dtUnknown, mempty) else do dName <- c_name dEnt >>= peekFilePath dType <- c_type dEnt c_freeDirEnt dEnt return (dType, dName) else do errno <- getErrno if errno == eINTR then loop ptr_dEnt else do let (Errno eo) = errno if eo == 0 then return (dtUnknown, mempty) else throwErrno "readDirEnt" newtype DirStreamPortable = DirStreamPortable (FilePath, DirStream) openDirStreamPortable :: FilePath -> IO DirStreamPortable openDirStreamPortable fp = do dirs <- openDirStream fp pure $ DirStreamPortable (fp, dirs) closeDirStreamPortable :: DirStreamPortable -> IO () closeDirStreamPortable (DirStreamPortable (_, dirs)) = closeDirStream dirs readDirEntPortable :: DirStreamPortable -> IO (DirType, FilePath) readDirEntPortable (DirStreamPortable (basedir, dirs)) = do (dt, fp) <- readDirEnt dirs case (dt, fp) of (DirType #{const DT_BLK}, _) -> pure (dt, fp) (DirType #{const DT_CHR}, _) -> pure (dt, fp) (DirType #{const DT_DIR}, _) -> pure (dt, fp) (DirType #{const DT_FIFO}, _) -> pure (dt, fp) (DirType #{const DT_LNK}, _) -> pure (dt, fp) (DirType #{const DT_REG}, _) -> pure (dt, fp) (DirType #{const DT_SOCK}, _) -> pure (dt, fp) (_, _) | fp /= "" -> do stat <- getSymbolicLinkStatus (basedir fp) pure $ (, fp) $ if | isBlockDevice stat -> DirType #{const DT_BLK} | isCharacterDevice stat -> DirType #{const DT_CHR} | isDirectory stat -> DirType #{const DT_DIR} | isNamedPipe stat -> DirType #{const DT_FIFO} | isSymbolicLink stat -> DirType #{const DT_LNK} | isRegularFile stat -> DirType #{const DT_REG} | isSocket stat -> DirType #{const DT_SOCK} | otherwise -> DirType #{const DT_UNKNOWN} | otherwise -> pure (dt, fp)