diff --git a/System/Posix/Directory.hsc b/System/Posix/Directory.hsc
--- a/System/Posix/Directory.hsc
+++ b/System/Posix/Directory.hsc
@@ -48,6 +48,7 @@
    changeWorkingDirectoryFd,
   ) where
 
+import Control.Monad ((>=>))
 import Data.Maybe
 import System.Posix.Error
 import System.Posix.Types
@@ -84,50 +85,21 @@
 -- | @readDirStream dp@ calls @readdir@ to obtain the
 --   next directory entry (@struct dirent@) for the open directory
 --   stream @dp@, and returns the @d_name@ member of that
---  structure.
+--   structure.
 --
---  Note that this function returns an empty filepath if the end of the
---  directory stream is reached. For a safer alternative use
---  'readDirStreamMaybe'.
+--   Note that this function returns an empty filepath if the end of the
+--   directory stream is reached. For a safer alternative use
+--   'readDirStreamMaybe'.
 readDirStream :: DirStream -> IO FilePath
 readDirStream = fmap (fromMaybe "") . readDirStreamMaybe
 
 -- | @readDirStreamMaybe dp@ calls @readdir@ to obtain the
 --   next directory entry (@struct dirent@) for the open directory
 --   stream @dp@. It returns the @d_name@ member of that
---  structure wrapped in a @Just d_name@ if an entry was read and @Nothing@ if
---  the end of the directory stream was reached.
+--   structure wrapped in a @Just d_name@ if an entry was read and @Nothing@ if
+--   the end of the directory stream was reached.
 readDirStreamMaybe :: DirStream -> IO (Maybe FilePath)
-readDirStreamMaybe (DirStream 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 Nothing
-                    else do
-                     entry <- (d_name dEnt >>= peekFilePath)
-                     c_freeDirEnt dEnt
-                     return $ Just entry
-         else do errno <- getErrno
-                 if (errno == eINTR) then loop ptr_dEnt else do
-                 let (Errno eo) = errno
-                 if (eo == 0)
-                    then return Nothing
-                    else throwErrno "readDirStream"
-
--- traversing directories
-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"
-  d_name :: Ptr CDirent -> IO CString
+readDirStreamMaybe = readDirStreamWith (dirEntName >=> peekFilePath)
 
 
 -- | @getWorkingDirectory@ calls @getcwd@ to obtain the name
diff --git a/System/Posix/Directory/ByteString.hsc b/System/Posix/Directory/ByteString.hsc
--- a/System/Posix/Directory/ByteString.hsc
+++ b/System/Posix/Directory/ByteString.hsc
@@ -48,6 +48,7 @@
    changeWorkingDirectoryFd,
   ) where
 
+import Control.Monad ((>=>))
 import Data.Maybe
 import System.Posix.Types
 import Foreign
@@ -60,7 +61,7 @@
 
 -- | @createDirectory dir mode@ calls @mkdir@ to
 --   create a new directory, @dir@, with permissions based on
---  @mode@.
+--   @mode@.
 createDirectory :: RawFilePath -> FileMode -> IO ()
 createDirectory name mode =
   withFilePath name $ \s ->
@@ -85,50 +86,21 @@
 -- | @readDirStream dp@ calls @readdir@ to obtain the
 --   next directory entry (@struct dirent@) for the open directory
 --   stream @dp@, and returns the @d_name@ member of that
---  structure.
+--   structure.
 --
---  Note that this function returns an empty filepath if the end of the
---  directory stream is reached. For a safer alternative use
---  'readDirStreamMaybe'.
+--   Note that this function returns an empty filepath if the end of the
+--   directory stream is reached. For a safer alternative use
+--   'readDirStreamMaybe'.
 readDirStream :: DirStream -> IO RawFilePath
 readDirStream = fmap (fromMaybe BC.empty) . readDirStreamMaybe
 
 -- | @readDirStreamMaybe dp@ calls @readdir@ to obtain the
 --   next directory entry (@struct dirent@) for the open directory
 --   stream @dp@. It returns the @d_name@ member of that
---  structure wrapped in a @Just d_name@ if an entry was read and @Nothing@ if
---  the end of the directory stream was reached.
+--   structure wrapped in a @Just d_name@ if an entry was read and @Nothing@ if
+--   the end of the directory stream was reached.
 readDirStreamMaybe :: DirStream -> IO (Maybe RawFilePath)
-readDirStreamMaybe (DirStream 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 Nothing
-                    else do
-                     entry <- (d_name dEnt >>= peekFilePath)
-                     c_freeDirEnt dEnt
-                     return $ Just entry
-         else do errno <- getErrno
-                 if (errno == eINTR) then loop ptr_dEnt else do
-                 let (Errno eo) = errno
-                 if (eo == 0)
-                    then return Nothing
-                    else throwErrno "readDirStream"
-
--- traversing directories
-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"
-  d_name :: Ptr CDirent -> IO CString
+readDirStreamMaybe = readDirStreamWith (dirEntName >=> peekFilePath)
 
 
 -- | @getWorkingDirectory@ calls @getcwd@ to obtain the name
diff --git a/System/Posix/Directory/Common.hsc b/System/Posix/Directory/Common.hsc
--- a/System/Posix/Directory/Common.hsc
+++ b/System/Posix/Directory/Common.hsc
@@ -1,4 +1,4 @@
-{-# LANGUAGE Safe, CApiFFI #-}
+{-# LANGUAGE CPP, Safe, CApiFFI, MultiWayIf, PatternSynonyms #-}
 
 -----------------------------------------------------------------------------
 -- |
@@ -15,10 +15,46 @@
 -----------------------------------------------------------------------------
 
 #include "HsUnix.h"
+#include "HsUnixConfig.h"
+##include "HsUnixConfig.h"
 
 module System.Posix.Directory.Common (
-       DirStream(..), CDir, CDirent, DirStreamOffset(..),
+       DirStream(..),
+       CDir,
+       CDirent,
+       DirStreamOffset(..),
+
+       DirStreamWithPath(..),
+       fromDirStreamWithPath,
+       toDirStreamWithPath,
+       DirEnt(..),
+       dirEntName,
+       dirEntType,
+       DirType( DirType
+              , UnknownType
+              , NamedPipeType
+              , CharacterDeviceType
+              , DirectoryType
+              , BlockDeviceType
+              , RegularFileType
+              , SymbolicLinkType
+              , SocketType
+              , WhiteoutType
+              ),
+       isUnknownType,
+       isNamedPipeType,
+       isCharacterDeviceType,
+       isDirectoryType,
+       isBlockDeviceType,
+       isRegularFileType,
+       isSymbolicLinkType,
+       isSocketType,
+       isWhiteoutType,
+       getRealDirType,
        unsafeOpenDirStreamFd,
+       readDirStreamWith,
+       readDirStreamWithPtr,
+
        rewindDirStream,
        closeDirStream,
 #ifdef HAVE_SEEKDIR
@@ -41,11 +77,176 @@
 import GHC.IO.Exception ( unsupportedOperation )
 #endif
 
+import System.Posix.Files.Common
+
 newtype DirStream = DirStream (Ptr CDir)
 
+-- | @since 2.8.6.0
+newtype DirStreamWithPath a = DirStreamWithPath (a, Ptr CDir)
+
+-- | Convert a 'DirStreamWithPath' to a 'DirStream'.
+-- Note that the underlying pointer is shared by both values, hence any
+-- modification to the resulting 'DirStream' will also modify the original
+-- 'DirStreamWithPath'.
+--
+-- @since 2.8.6.0
+fromDirStreamWithPath :: DirStreamWithPath a -> DirStream
+fromDirStreamWithPath (DirStreamWithPath (_, ptr)) = DirStream ptr
+
+-- | Construct a 'DirStreamWithPath' from a 'DirStream'.
+-- Note that the underlying pointer is shared by both values, hence any
+-- modification to the pointer of the resulting 'DirStreamWithPath' will also
+-- modify the original 'DirStream'.
+--
+-- @since 2.8.6.0
+toDirStreamWithPath :: a -> DirStream -> DirStreamWithPath a
+toDirStreamWithPath path (DirStream ptr) = DirStreamWithPath (path, ptr)
+
+-- | @since 2.8.6.0
+newtype DirEnt = DirEnt (Ptr CDirent)
+
+-- We provide a hand-written instance here since GeneralizedNewtypeDeriving and
+-- DerivingVia are not allowed in Safe Haskell.
+instance Storable DirEnt where
+  sizeOf _ = sizeOf (undefined :: Ptr CDirent)
+  {-# INLINE sizeOf #-}
+
+  alignment _ = alignment (undefined :: Ptr CDirent)
+  {-# INLINE alignment #-}
+
+  peek ptr = DirEnt <$> peek (castPtr ptr)
+  {-# INLINE peek #-}
+
+  poke ptr (DirEnt dEnt) = poke (castPtr ptr) dEnt
+  {-# INLINE poke#-}
+
 data {-# CTYPE "DIR" #-} CDir
 data {-# CTYPE "struct dirent" #-} CDirent
 
+-- | The value of the @d_type@ field of a @dirent@ struct.
+-- Note that the possible values of that type depend on the filesystem that is
+-- queried. From @readdir(3)@:
+--
+-- > Currently, only some filesystems (among them: Btrfs, ext2, ext3, and ext4)
+-- > have full support for returning the file type in d_type. All applications
+-- > must properly handle a return of DT_UNKNOWN.
+--
+-- For example, JFS is a filesystem that does not support @d_type@;
+-- See https://github.com/haskell/ghcup-hs/issues/766
+--
+-- Furthermore, @dirent@ or the constants represented by the associated pattern
+-- synonyms of this type may not be provided by the underlying platform. In that
+-- case none of those patterns will match and the application must handle that
+-- case accordingly.
+--
+-- @since 2.8.6.0
+newtype DirType = DirType CChar
+    deriving (Eq, Ord, Show)
+
+-- | The 'DirType' refers to an entry of unknown type.
+pattern UnknownType :: DirType
+pattern UnknownType = DirType (CONST_DT_UNKNOWN)
+
+-- | The 'DirType' refers to an entry that is a named pipe.
+pattern NamedPipeType :: DirType
+pattern NamedPipeType = DirType (CONST_DT_FIFO)
+
+-- | The 'DirType' refers to an entry that is a character device.
+pattern CharacterDeviceType :: DirType
+pattern CharacterDeviceType = DirType (CONST_DT_CHR)
+
+-- | The 'DirType' refers to an entry that is a directory.
+pattern DirectoryType :: DirType
+pattern DirectoryType = DirType (CONST_DT_DIR)
+
+-- | The 'DirType' refers to an entry that is a block device.
+pattern BlockDeviceType :: DirType
+pattern BlockDeviceType = DirType (CONST_DT_BLK)
+
+-- | The 'DirType' refers to an entry that is a regular file.
+pattern RegularFileType :: DirType
+pattern RegularFileType = DirType (CONST_DT_REG)
+
+-- | The 'DirType' refers to an entry that is a symbolic link.
+pattern SymbolicLinkType :: DirType
+pattern SymbolicLinkType = DirType (CONST_DT_LNK)
+
+-- | The 'DirType' refers to an entry that is a socket.
+pattern SocketType :: DirType
+pattern SocketType = DirType (CONST_DT_SOCK)
+
+-- | The 'DirType' refers to an entry that is a whiteout.
+pattern WhiteoutType :: DirType
+pattern WhiteoutType = DirType (CONST_DT_WHT)
+
+-- | Checks if this 'DirType' refers to an entry of unknown type.
+--
+-- @since 2.8.6.0
+isUnknownType         :: DirType -> Bool
+-- | Checks if this 'DirType' refers to a block device entry.
+--
+-- @since 2.8.6.0
+isBlockDeviceType     :: DirType -> Bool
+-- | Checks if this 'DirType' refers to a character device entry.
+--
+-- @since 2.8.6.0
+isCharacterDeviceType :: DirType -> Bool
+-- | Checks if this 'DirType' refers to a named pipe entry.
+--
+-- @since 2.8.6.0
+isNamedPipeType       :: DirType -> Bool
+-- | Checks if this 'DirType' refers to a regular file entry.
+--
+-- @since 2.8.6.0
+isRegularFileType     :: DirType -> Bool
+-- | Checks if this 'DirType' refers to a directory entry.
+--
+-- @since 2.8.6.0
+isDirectoryType       :: DirType -> Bool
+-- | Checks if this 'DirType' refers to a symbolic link entry.
+--
+-- @since 2.8.6.0
+isSymbolicLinkType    :: DirType -> Bool
+-- | Checks if this 'DirType' refers to a socket entry.
+--
+-- @since 2.8.6.0
+isSocketType          :: DirType -> Bool
+-- | Checks if this 'DirType' refers to a whiteout entry.
+--
+-- @since 2.8.6.0
+isWhiteoutType        :: DirType -> Bool
+
+isUnknownType dtype = dtype == UnknownType
+isBlockDeviceType dtype = dtype == BlockDeviceType
+isCharacterDeviceType dtype = dtype == CharacterDeviceType
+isNamedPipeType dtype = dtype == NamedPipeType
+isRegularFileType dtype = dtype == RegularFileType
+isDirectoryType dtype = dtype == DirectoryType
+isSymbolicLinkType dtype = dtype == SymbolicLinkType
+isSocketType dtype = dtype == SocketType
+isWhiteoutType dtype = dtype == WhiteoutType
+
+-- | @since 2.8.6.0
+getRealDirType :: IO FileStatus -> DirType -> IO DirType
+getRealDirType _ BlockDeviceType = return BlockDeviceType
+getRealDirType _ CharacterDeviceType = return CharacterDeviceType
+getRealDirType _ NamedPipeType = return NamedPipeType
+getRealDirType _ RegularFileType = return RegularFileType
+getRealDirType _ DirectoryType = return DirectoryType
+getRealDirType _ SymbolicLinkType = return SymbolicLinkType
+getRealDirType _ SocketType = return SocketType
+getRealDirType _ WhiteoutType = return WhiteoutType
+getRealDirType getFileStatus _ = do
+    stat <- getFileStatus
+    return $ if | isRegularFile stat -> RegularFileType
+                | isDirectory stat -> DirectoryType
+                | isSymbolicLink stat -> SymbolicLinkType
+                | isBlockDevice stat -> BlockDeviceType
+                | isCharacterDevice stat -> CharacterDeviceType
+                | isNamedPipe stat -> NamedPipeType
+                | isSocket stat -> SocketType
+                | otherwise -> UnknownType
+
 -- | Call @fdopendir@ to obtain a directory stream for @fd@. @fd@ must not be
 -- otherwise used after this.
 --
@@ -57,6 +258,8 @@
 --
 -- The input file descriptor must not have been used with @threadWaitRead@ or
 -- @threadWaitWrite@.
+--
+-- @since 2.8.6.0
 unsafeOpenDirStreamFd :: Fd -> IO DirStream
 unsafeOpenDirStreamFd (Fd fd) = mask_ $ do
     ptr <- c_fdopendir fd
@@ -77,6 +280,77 @@
 --
 foreign import capi unsafe "dirent.h fdopendir"
     c_fdopendir :: CInt -> IO (Ptr CDir)
+
+
+-- | @readDirStreamWith f dp@ calls @readdir@ to obtain the next directory entry
+--   (@struct dirent@) for the open directory stream @dp@. If an entry is read,
+--   it passes the pointer to that structure to the provided function @f@ for
+--   processing. It returns the result of that function call wrapped in a @Just@
+--   if an entry was read and @Nothing@ if the end of the directory stream was
+--   reached.
+--
+--   __NOTE:__ The lifetime of the pointer wrapped in the `DirEnt` is limited to
+--   invocation of the callback and it will be freed automatically after. Do not
+--   pass it to the outside world!
+--
+-- @since 2.8.6.0
+readDirStreamWith :: (DirEnt -> IO a) -> DirStream -> IO (Maybe a)
+readDirStreamWith f dstream = alloca
+  (\ptr_dEnt  -> readDirStreamWithPtr ptr_dEnt f dstream)
+
+-- | A version of 'readDirStreamWith' that takes a pre-allocated pointer in
+--   addition to the other arguments. This pointer is used to store the pointer
+--   to the next directory entry, if there is any. This function is intended for
+--   use cases where you need to read a lot of directory entries and want to
+--   reuse the pointer for each of them. Using for example 'readDirStream' or
+--   'readDirStreamWith' in this scenario would allocate a new pointer for each
+--   call of these functions.
+--
+--   __NOTE__: You are responsible for releasing the pointer after you are done.
+--
+-- @since 2.8.6.0
+readDirStreamWithPtr :: Ptr DirEnt -> (DirEnt -> IO a) -> DirStream -> IO (Maybe a)
+readDirStreamWithPtr ptr_dEnt f dstream@(DirStream dirp) = do
+  resetErrno
+  r <- c_readdir dirp (castPtr ptr_dEnt)
+  if (r == 0)
+       then do dEnt@(DirEnt dEntPtr) <- peek ptr_dEnt
+               if (dEntPtr == nullPtr)
+                  then return Nothing
+                  else do
+                   res <- f dEnt
+                   c_freeDirEnt dEntPtr
+                   return (Just res)
+       else do errno <- getErrno
+               if (errno == eINTR)
+                  then readDirStreamWithPtr ptr_dEnt f dstream
+                  else do
+                   let (Errno eo) = errno
+                   if (eo == 0)
+                      then return Nothing
+                      else throwErrno "readDirStream"
+
+-- | @since 2.8.6.0
+dirEntName :: DirEnt -> IO CString
+dirEntName (DirEnt dEntPtr) = d_name dEntPtr
+
+foreign import ccall unsafe "__hscore_d_name"
+  d_name :: Ptr CDirent -> IO CString
+
+-- | @since 2.8.6.0
+dirEntType :: DirEnt -> IO DirType
+dirEntType (DirEnt dEntPtr) = DirType <$> d_type dEntPtr
+
+foreign import ccall unsafe "__hscore_d_type"
+  d_type :: Ptr CDirent -> IO CChar
+
+-- traversing directories
+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 ()
+
 
 -- | @rewindDirStream dp@ calls @rewinddir@ to reposition
 --   the directory stream @dp@ at the beginning of the directory.
diff --git a/System/Posix/Directory/Internals.hsc b/System/Posix/Directory/Internals.hsc
--- a/System/Posix/Directory/Internals.hsc
+++ b/System/Posix/Directory/Internals.hsc
@@ -12,6 +12,41 @@
 --
 -----------------------------------------------------------------------------
 
-module System.Posix.Directory.Internals ( DirStream(..), CDir, CDirent, DirStreamOffset(..) ) where
+module System.Posix.Directory.Internals (
+    DirStream(..),
+    CDir,
+    CDirent,
+    DirStreamOffset(..),
+
+    DirStreamWithPath(..),
+    fromDirStreamWithPath,
+    toDirStreamWithPath,
+    DirEnt(..),
+    dirEntName,
+    dirEntType,
+    DirType( DirType
+           , UnknownType
+           , NamedPipeType
+           , CharacterDeviceType
+           , DirectoryType
+           , BlockDeviceType
+           , RegularFileType
+           , SymbolicLinkType
+           , SocketType
+           , WhiteoutType
+           ),
+    isUnknownType,
+    isNamedPipeType,
+    isCharacterDeviceType,
+    isDirectoryType,
+    isBlockDeviceType,
+    isRegularFileType,
+    isSymbolicLinkType,
+    isSocketType,
+    isWhiteoutType,
+    getRealDirType,
+    readDirStreamWith,
+    readDirStreamWithPtr,
+    ) where
 
 import System.Posix.Directory.Common
diff --git a/System/Posix/Directory/PosixPath.hsc b/System/Posix/Directory/PosixPath.hsc
--- a/System/Posix/Directory/PosixPath.hsc
+++ b/System/Posix/Directory/PosixPath.hsc
@@ -27,37 +27,39 @@
    createDirectory, removeDirectory,
 
    -- * Reading directories
-   DirStream,
+   Common.DirStream,
    openDirStream,
    readDirStream,
-   rewindDirStream,
-   closeDirStream,
-   DirStreamOffset,
+   readDirStreamMaybe,
+   Common.rewindDirStream,
+   Common.closeDirStream,
+   Common.DirStreamOffset,
 #ifdef HAVE_TELLDIR
-   tellDirStream,
+   Common.tellDirStream,
 #endif
 #ifdef HAVE_SEEKDIR
-   seekDirStream,
+   Common.seekDirStream,
 #endif
 
    -- * The working directory
    getWorkingDirectory,
    changeWorkingDirectory,
-   changeWorkingDirectoryFd,
+   Common.changeWorkingDirectoryFd,
   ) where
 
+import Control.Monad ((>=>))
+import Data.Maybe
 import System.Posix.Types
 import Foreign
 import Foreign.C
 
-import System.OsPath.Types
-import System.Posix.Directory hiding (createDirectory, openDirStream, readDirStream, getWorkingDirectory, changeWorkingDirectory, removeDirectory)
+import System.OsPath.Posix
 import qualified System.Posix.Directory.Common as Common
 import System.Posix.PosixPath.FilePath
 
 -- | @createDirectory dir mode@ calls @mkdir@ to
 --   create a new directory, @dir@, with permissions based on
---  @mode@.
+--   @mode@.
 createDirectory :: PosixPath -> FileMode -> IO ()
 createDirectory name mode =
   withFilePath name $ \s ->
@@ -70,7 +72,7 @@
 
 -- | @openDirStream dir@ calls @opendir@ to obtain a
 --   directory stream for @dir@.
-openDirStream :: PosixPath -> IO DirStream
+openDirStream :: PosixPath -> IO Common.DirStream
 openDirStream name =
   withFilePath name $ \s -> do
     dirp <- throwErrnoPathIfNullRetry "openDirStream" name $ c_opendir s
@@ -82,37 +84,22 @@
 -- | @readDirStream dp@ calls @readdir@ to obtain the
 --   next directory entry (@struct dirent@) for the open directory
 --   stream @dp@, and returns the @d_name@ member of that
---  structure.
-readDirStream :: DirStream -> IO PosixPath
-readDirStream (Common.DirStream 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 mempty
-                    else do
-                     entry <- (d_name dEnt >>= peekFilePath)
-                     c_freeDirEnt dEnt
-                     return entry
-         else do errno <- getErrno
-                 if (errno == eINTR) then loop ptr_dEnt else do
-                 let (Errno eo) = errno
-                 if (eo == 0)
-                    then return mempty
-                    else throwErrno "readDirStream"
-
--- traversing directories
-foreign import ccall unsafe "__hscore_readdir"
-  c_readdir  :: Ptr Common.CDir -> Ptr (Ptr Common.CDirent) -> IO CInt
-
-foreign import ccall unsafe "__hscore_free_dirent"
-  c_freeDirEnt  :: Ptr Common.CDirent -> IO ()
+--   structure.
+--
+--   Note that this function returns an empty filepath if the end of the
+--   directory stream is reached. For a safer alternative use
+--   'readDirStreamMaybe'.
+readDirStream :: Common.DirStream -> IO PosixPath
+readDirStream = fmap (fromMaybe mempty) . readDirStreamMaybe
 
-foreign import ccall unsafe "__hscore_d_name"
-  d_name :: Ptr Common.CDirent -> IO CString
+-- | @readDirStreamMaybe dp@ calls @readdir@ to obtain the
+--   next directory entry (@struct dirent@) for the open directory
+--   stream @dp@. It returns the @d_name@ member of that
+--   structure wrapped in a @Just d_name@ if an entry was read and @Nothing@ if
+--   the end of the directory stream was reached.
+readDirStreamMaybe :: Common.DirStream -> IO (Maybe PosixPath)
+readDirStreamMaybe = Common.readDirStreamWith
+  (Common.dirEntName >=> peekFilePath)
 
 
 -- | @getWorkingDirectory@ calls @getcwd@ to obtain the name
diff --git a/System/Posix/Files/Common.hsc b/System/Posix/Files/Common.hsc
--- a/System/Posix/Files/Common.hsc
+++ b/System/Posix/Files/Common.hsc
@@ -551,7 +551,7 @@
     c_futimens :: CInt -> Ptr CTimeSpec -> IO CInt
 #endif
 
-data CTimeVal = CTimeVal CLong CLong
+data CTimeVal = CTimeVal (#type time_t) (#type suseconds_t)
 
 instance Storable CTimeVal where
     sizeOf    _ = #size struct timeval
diff --git a/cbits/HsUnix.c b/cbits/HsUnix.c
--- a/cbits/HsUnix.c
+++ b/cbits/HsUnix.c
@@ -104,6 +104,15 @@
   return (d->d_name);
 }
 
+char __hscore_d_type( struct dirent* d )
+{
+#ifdef HAVE_DIRENT_D_TYPE
+  return (d->d_type);
+#else
+  return CONST_DT_UNKNOWN;
+#endif
+}
+
 void __hscore_free_dirent(struct dirent *dEnt)
 {
 #if HAVE_READDIR_R && USE_READDIR_R
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,6 +1,11 @@
 # Changelog for [`unix` package](http://hackage.haskell.org/package/unix)
 
-## 2.8.5.1 *Apr 2023*
+## 2.8.6.0 *Nov 2024*
+
+  * add `readDirStreamWith` and `readDirStreamWithPtr` to `System.Posix.Directory.Internals` wrt [#251](https://github.com/haskell/unix/pull/251)
+  * Fix CTimeVal definition for platforms where time_t isn't CLong, wrt [#252](https://github.com/haskell/unix/pull/252)
+
+## 2.8.5.1 *Apr 2024*
 
   * fix building with newer filepath/os-string when `#ifndef HAVE_OPENPTY`
 
diff --git a/configure b/configure
--- a/configure
+++ b/configure
@@ -1562,215 +1562,6 @@
 
 } # ac_fn_c_try_link
 
-# ac_fn_c_check_func LINENO FUNC VAR
-# ----------------------------------
-# Tests whether FUNC exists, setting the cache variable VAR accordingly
-ac_fn_c_check_func ()
-{
-  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
-  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
-printf %s "checking for $2... " >&6; }
-if eval test \${$3+y}
-then :
-  printf %s "(cached) " >&6
-else $as_nop
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-/* Define $2 to an innocuous variant, in case <limits.h> declares $2.
-   For example, HP-UX 11i <limits.h> declares gettimeofday.  */
-#define $2 innocuous_$2
-
-/* System header to define __stub macros and hopefully few prototypes,
-   which can conflict with char $2 (); below.  */
-
-#include <limits.h>
-#undef $2
-
-/* Override any GCC internal prototype to avoid an error.
-   Use char because int might match the return type of a GCC
-   builtin and then its argument prototype would still apply.  */
-#ifdef __cplusplus
-extern "C"
-#endif
-char $2 ();
-/* The GNU C library defines this for functions which it implements
-    to always fail with ENOSYS.  Some functions are actually named
-    something starting with __ and the normal name is an alias.  */
-#if defined __stub_$2 || defined __stub___$2
-choke me
-#endif
-
-int
-main (void)
-{
-return $2 ();
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_link "$LINENO"
-then :
-  eval "$3=yes"
-else $as_nop
-  eval "$3=no"
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.beam \
-    conftest$ac_exeext conftest.$ac_ext
-fi
-eval ac_res=\$$3
-	       { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
-printf "%s\n" "$ac_res" >&6; }
-  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
-
-} # ac_fn_c_check_func
-
-# ac_fn_c_check_type LINENO TYPE VAR INCLUDES
-# -------------------------------------------
-# Tests whether TYPE exists after having included INCLUDES, setting cache
-# variable VAR accordingly.
-ac_fn_c_check_type ()
-{
-  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
-  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
-printf %s "checking for $2... " >&6; }
-if eval test \${$3+y}
-then :
-  printf %s "(cached) " >&6
-else $as_nop
-  eval "$3=no"
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-$4
-int
-main (void)
-{
-if (sizeof ($2))
-	 return 0;
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"
-then :
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-$4
-int
-main (void)
-{
-if (sizeof (($2)))
-	    return 0;
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"
-then :
-
-else $as_nop
-  eval "$3=yes"
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
-fi
-eval ac_res=\$$3
-	       { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
-printf "%s\n" "$ac_res" >&6; }
-  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
-
-} # ac_fn_c_check_type
-
-# ac_fn_c_try_cpp LINENO
-# ----------------------
-# Try to preprocess conftest.$ac_ext, and return whether this succeeded.
-ac_fn_c_try_cpp ()
-{
-  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
-  if { { ac_try="$ac_cpp conftest.$ac_ext"
-case "(($ac_try" in
-  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
-  *) ac_try_echo=$ac_try;;
-esac
-eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
-printf "%s\n" "$ac_try_echo"; } >&5
-  (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err
-  ac_status=$?
-  if test -s conftest.err; then
-    grep -v '^ *+' conftest.err >conftest.er1
-    cat conftest.er1 >&5
-    mv -f conftest.er1 conftest.err
-  fi
-  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; } > conftest.i && {
-	 test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" ||
-	 test ! -s conftest.err
-       }
-then :
-  ac_retval=0
-else $as_nop
-  printf "%s\n" "$as_me: failed program was:" >&5
-sed 's/^/| /' conftest.$ac_ext >&5
-
-    ac_retval=1
-fi
-  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
-  as_fn_set_status $ac_retval
-
-} # ac_fn_c_try_cpp
-
-# ac_fn_check_decl LINENO SYMBOL VAR INCLUDES EXTRA-OPTIONS FLAG-VAR
-# ------------------------------------------------------------------
-# Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR
-# accordingly. Pass EXTRA-OPTIONS to the compiler, using FLAG-VAR.
-ac_fn_check_decl ()
-{
-  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
-  as_decl_name=`echo $2|sed 's/ *(.*//'`
-  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $as_decl_name is declared" >&5
-printf %s "checking whether $as_decl_name is declared... " >&6; }
-if eval test \${$3+y}
-then :
-  printf %s "(cached) " >&6
-else $as_nop
-  as_decl_use=`echo $2|sed -e 's/(/((/' -e 's/)/) 0&/' -e 's/,/) 0& (/g'`
-  eval ac_save_FLAGS=\$$6
-  as_fn_append $6 " $5"
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-$4
-int
-main (void)
-{
-#ifndef $as_decl_name
-#ifdef __cplusplus
-  (void) $as_decl_use;
-#else
-  (void) $as_decl_name;
-#endif
-#endif
-
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"
-then :
-  eval "$3=yes"
-else $as_nop
-  eval "$3=no"
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
-  eval $6=\$ac_save_FLAGS
-
-fi
-eval ac_res=\$$3
-	       { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
-printf "%s\n" "$ac_res" >&6; }
-  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
-
-} # ac_fn_check_decl
-
 # ac_fn_c_check_member LINENO AGGR MEMBER VAR INCLUDES
 # ----------------------------------------------------
 # Tries to find if the field MEMBER exists in type AGGR, after including
@@ -2062,6 +1853,215 @@
   as_fn_set_status $ac_retval
 
 } # ac_fn_c_compute_int
+
+# ac_fn_c_check_func LINENO FUNC VAR
+# ----------------------------------
+# Tests whether FUNC exists, setting the cache variable VAR accordingly
+ac_fn_c_check_func ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+printf %s "checking for $2... " >&6; }
+if eval test \${$3+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+/* Define $2 to an innocuous variant, in case <limits.h> declares $2.
+   For example, HP-UX 11i <limits.h> declares gettimeofday.  */
+#define $2 innocuous_$2
+
+/* System header to define __stub macros and hopefully few prototypes,
+   which can conflict with char $2 (); below.  */
+
+#include <limits.h>
+#undef $2
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char $2 ();
+/* The GNU C library defines this for functions which it implements
+    to always fail with ENOSYS.  Some functions are actually named
+    something starting with __ and the normal name is an alias.  */
+#if defined __stub_$2 || defined __stub___$2
+choke me
+#endif
+
+int
+main (void)
+{
+return $2 ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"
+then :
+  eval "$3=yes"
+else $as_nop
+  eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+    conftest$ac_exeext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+	       { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+printf "%s\n" "$ac_res" >&6; }
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_func
+
+# ac_fn_c_check_type LINENO TYPE VAR INCLUDES
+# -------------------------------------------
+# Tests whether TYPE exists after having included INCLUDES, setting cache
+# variable VAR accordingly.
+ac_fn_c_check_type ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+printf %s "checking for $2... " >&6; }
+if eval test \${$3+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  eval "$3=no"
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+int
+main (void)
+{
+if (sizeof ($2))
+	 return 0;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+int
+main (void)
+{
+if (sizeof (($2)))
+	    return 0;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+
+else $as_nop
+  eval "$3=yes"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+eval ac_res=\$$3
+	       { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+printf "%s\n" "$ac_res" >&6; }
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_type
+
+# ac_fn_c_try_cpp LINENO
+# ----------------------
+# Try to preprocess conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_cpp ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if { { ac_try="$ac_cpp conftest.$ac_ext"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+printf "%s\n" "$ac_try_echo"; } >&5
+  (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } > conftest.i && {
+	 test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" ||
+	 test ! -s conftest.err
+       }
+then :
+  ac_retval=0
+else $as_nop
+  printf "%s\n" "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+    ac_retval=1
+fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_cpp
+
+# ac_fn_check_decl LINENO SYMBOL VAR INCLUDES EXTRA-OPTIONS FLAG-VAR
+# ------------------------------------------------------------------
+# Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR
+# accordingly. Pass EXTRA-OPTIONS to the compiler, using FLAG-VAR.
+ac_fn_check_decl ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  as_decl_name=`echo $2|sed 's/ *(.*//'`
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $as_decl_name is declared" >&5
+printf %s "checking whether $as_decl_name is declared... " >&6; }
+if eval test \${$3+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  as_decl_use=`echo $2|sed -e 's/(/((/' -e 's/)/) 0&/' -e 's/,/) 0& (/g'`
+  eval ac_save_FLAGS=\$$6
+  as_fn_append $6 " $5"
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+int
+main (void)
+{
+#ifndef $as_decl_name
+#ifdef __cplusplus
+  (void) $as_decl_use;
+#else
+  (void) $as_decl_name;
+#endif
+#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+  eval "$3=yes"
+else $as_nop
+  eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+  eval $6=\$ac_save_FLAGS
+
+fi
+eval ac_res=\$$3
+	       { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+printf "%s\n" "$ac_res" >&6; }
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_check_decl
 ac_configure_args_raw=
 for ac_arg
 do
@@ -4444,6 +4444,410 @@
   printf "%s\n" "#define HAVE_UTIME_H 1" >>confdefs.h
 
 fi
+
+
+ac_header_dirent=no
+for ac_hdr in dirent.h sys/ndir.h sys/dir.h ndir.h; do
+  as_ac_Header=`printf "%s\n" "ac_cv_header_dirent_$ac_hdr" | $as_tr_sh`
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_hdr that defines DIR" >&5
+printf %s "checking for $ac_hdr that defines DIR... " >&6; }
+if eval test \${$as_ac_Header+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <sys/types.h>
+#include <$ac_hdr>
+
+int
+main (void)
+{
+if ((DIR *) 0)
+return 0;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+  eval "$as_ac_Header=yes"
+else $as_nop
+  eval "$as_ac_Header=no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+eval ac_res=\$$as_ac_Header
+	       { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+printf "%s\n" "$ac_res" >&6; }
+if eval test \"x\$"$as_ac_Header"\" = x"yes"
+then :
+  cat >>confdefs.h <<_ACEOF
+#define `printf "%s\n" "HAVE_$ac_hdr" | $as_tr_cpp` 1
+_ACEOF
+
+ac_header_dirent=$ac_hdr; break
+fi
+
+done
+# Two versions of opendir et al. are in -ldir and -lx on SCO Xenix.
+if test $ac_header_dirent = dirent.h; then
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing opendir" >&5
+printf %s "checking for library containing opendir... " >&6; }
+if test ${ac_cv_search_opendir+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+char opendir ();
+int
+main (void)
+{
+return opendir ();
+  ;
+  return 0;
+}
+_ACEOF
+for ac_lib in '' dir
+do
+  if test -z "$ac_lib"; then
+    ac_res="none required"
+  else
+    ac_res=-l$ac_lib
+    LIBS="-l$ac_lib  $ac_func_search_save_LIBS"
+  fi
+  if ac_fn_c_try_link "$LINENO"
+then :
+  ac_cv_search_opendir=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+    conftest$ac_exeext
+  if test ${ac_cv_search_opendir+y}
+then :
+  break
+fi
+done
+if test ${ac_cv_search_opendir+y}
+then :
+
+else $as_nop
+  ac_cv_search_opendir=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_opendir" >&5
+printf "%s\n" "$ac_cv_search_opendir" >&6; }
+ac_res=$ac_cv_search_opendir
+if test "$ac_res" != no
+then :
+  test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+fi
+
+else
+  { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing opendir" >&5
+printf %s "checking for library containing opendir... " >&6; }
+if test ${ac_cv_search_opendir+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+char opendir ();
+int
+main (void)
+{
+return opendir ();
+  ;
+  return 0;
+}
+_ACEOF
+for ac_lib in '' x
+do
+  if test -z "$ac_lib"; then
+    ac_res="none required"
+  else
+    ac_res=-l$ac_lib
+    LIBS="-l$ac_lib  $ac_func_search_save_LIBS"
+  fi
+  if ac_fn_c_try_link "$LINENO"
+then :
+  ac_cv_search_opendir=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam \
+    conftest$ac_exeext
+  if test ${ac_cv_search_opendir+y}
+then :
+  break
+fi
+done
+if test ${ac_cv_search_opendir+y}
+then :
+
+else $as_nop
+  ac_cv_search_opendir=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_opendir" >&5
+printf "%s\n" "$ac_cv_search_opendir" >&6; }
+ac_res=$ac_cv_search_opendir
+if test "$ac_res" != no
+then :
+  test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+fi
+
+fi
+
+
+
+  ac_fn_c_check_member "$LINENO" "struct dirent" "d_type" "ac_cv_member_struct_dirent_d_type" "
+#include <sys/types.h>
+#ifdef HAVE_DIRENT_H
+# include <dirent.h>
+#else
+# define dirent direct
+# ifdef HAVE_SYS_NDIR_H
+#  include <sys/ndir.h>
+# endif
+# ifdef HAVE_SYS_DIR_H
+#  include <sys/dir.h>
+# endif
+# ifdef HAVE_NDIR_H
+#  include <ndir.h>
+# endif
+#endif
+
+"
+if test "x$ac_cv_member_struct_dirent_d_type" = xyes
+then :
+
+printf "%s\n" "#define HAVE_STRUCT_DIRENT_D_TYPE 1" >>confdefs.h
+
+
+fi
+
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking value of DT_UNKNOWN" >&5
+printf %s "checking value of DT_UNKNOWN... " >&6; }
+if test ${fp_cv_const_DT_UNKNOWN+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  if ac_fn_c_compute_int "$LINENO" "DT_UNKNOWN" "fp_check_const_result"        "
+#if HAVE_STRUCT_DIRENT_D_TYPE
+#include <dirent.h>
+#endif
+"
+then :
+
+else $as_nop
+  fp_check_const_result=-1
+fi
+
+fp_cv_const_DT_UNKNOWN=$fp_check_const_result
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $fp_cv_const_DT_UNKNOWN" >&5
+printf "%s\n" "$fp_cv_const_DT_UNKNOWN" >&6; }
+printf "%s\n" "#define CONST_DT_UNKNOWN $fp_cv_const_DT_UNKNOWN" >>confdefs.h
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking value of DT_FIFO" >&5
+printf %s "checking value of DT_FIFO... " >&6; }
+if test ${fp_cv_const_DT_FIFO+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  if ac_fn_c_compute_int "$LINENO" "DT_FIFO" "fp_check_const_result"        "
+#if HAVE_STRUCT_DIRENT_D_TYPE
+#include <dirent.h>
+#endif
+"
+then :
+
+else $as_nop
+  fp_check_const_result=-2
+fi
+
+fp_cv_const_DT_FIFO=$fp_check_const_result
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $fp_cv_const_DT_FIFO" >&5
+printf "%s\n" "$fp_cv_const_DT_FIFO" >&6; }
+printf "%s\n" "#define CONST_DT_FIFO $fp_cv_const_DT_FIFO" >>confdefs.h
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking value of DT_CHR" >&5
+printf %s "checking value of DT_CHR... " >&6; }
+if test ${fp_cv_const_DT_CHR+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  if ac_fn_c_compute_int "$LINENO" "DT_CHR" "fp_check_const_result"        "
+#if HAVE_STRUCT_DIRENT_D_TYPE
+#include <dirent.h>
+#endif
+"
+then :
+
+else $as_nop
+  fp_check_const_result=-3
+fi
+
+fp_cv_const_DT_CHR=$fp_check_const_result
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $fp_cv_const_DT_CHR" >&5
+printf "%s\n" "$fp_cv_const_DT_CHR" >&6; }
+printf "%s\n" "#define CONST_DT_CHR $fp_cv_const_DT_CHR" >>confdefs.h
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking value of DT_DIR" >&5
+printf %s "checking value of DT_DIR... " >&6; }
+if test ${fp_cv_const_DT_DIR+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  if ac_fn_c_compute_int "$LINENO" "DT_DIR" "fp_check_const_result"        "
+#if HAVE_STRUCT_DIRENT_D_TYPE
+#include <dirent.h>
+#endif
+"
+then :
+
+else $as_nop
+  fp_check_const_result=-4
+fi
+
+fp_cv_const_DT_DIR=$fp_check_const_result
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $fp_cv_const_DT_DIR" >&5
+printf "%s\n" "$fp_cv_const_DT_DIR" >&6; }
+printf "%s\n" "#define CONST_DT_DIR $fp_cv_const_DT_DIR" >>confdefs.h
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking value of DT_BLK" >&5
+printf %s "checking value of DT_BLK... " >&6; }
+if test ${fp_cv_const_DT_BLK+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  if ac_fn_c_compute_int "$LINENO" "DT_BLK" "fp_check_const_result"        "
+#if HAVE_STRUCT_DIRENT_D_TYPE
+#include <dirent.h>
+#endif
+"
+then :
+
+else $as_nop
+  fp_check_const_result=-5
+fi
+
+fp_cv_const_DT_BLK=$fp_check_const_result
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $fp_cv_const_DT_BLK" >&5
+printf "%s\n" "$fp_cv_const_DT_BLK" >&6; }
+printf "%s\n" "#define CONST_DT_BLK $fp_cv_const_DT_BLK" >>confdefs.h
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking value of DT_REG" >&5
+printf %s "checking value of DT_REG... " >&6; }
+if test ${fp_cv_const_DT_REG+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  if ac_fn_c_compute_int "$LINENO" "DT_REG" "fp_check_const_result"        "
+#if HAVE_STRUCT_DIRENT_D_TYPE
+#include <dirent.h>
+#endif
+"
+then :
+
+else $as_nop
+  fp_check_const_result=-6
+fi
+
+fp_cv_const_DT_REG=$fp_check_const_result
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $fp_cv_const_DT_REG" >&5
+printf "%s\n" "$fp_cv_const_DT_REG" >&6; }
+printf "%s\n" "#define CONST_DT_REG $fp_cv_const_DT_REG" >>confdefs.h
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking value of DT_LNK" >&5
+printf %s "checking value of DT_LNK... " >&6; }
+if test ${fp_cv_const_DT_LNK+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  if ac_fn_c_compute_int "$LINENO" "DT_LNK" "fp_check_const_result"        "
+#if HAVE_STRUCT_DIRENT_D_TYPE
+#include <dirent.h>
+#endif
+"
+then :
+
+else $as_nop
+  fp_check_const_result=-7
+fi
+
+fp_cv_const_DT_LNK=$fp_check_const_result
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $fp_cv_const_DT_LNK" >&5
+printf "%s\n" "$fp_cv_const_DT_LNK" >&6; }
+printf "%s\n" "#define CONST_DT_LNK $fp_cv_const_DT_LNK" >>confdefs.h
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking value of DT_SOCK" >&5
+printf %s "checking value of DT_SOCK... " >&6; }
+if test ${fp_cv_const_DT_SOCK+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  if ac_fn_c_compute_int "$LINENO" "DT_SOCK" "fp_check_const_result"        "
+#if HAVE_STRUCT_DIRENT_D_TYPE
+#include <dirent.h>
+#endif
+"
+then :
+
+else $as_nop
+  fp_check_const_result=-8
+fi
+
+fp_cv_const_DT_SOCK=$fp_check_const_result
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $fp_cv_const_DT_SOCK" >&5
+printf "%s\n" "$fp_cv_const_DT_SOCK" >&6; }
+printf "%s\n" "#define CONST_DT_SOCK $fp_cv_const_DT_SOCK" >>confdefs.h
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking value of DT_WHT" >&5
+printf %s "checking value of DT_WHT... " >&6; }
+if test ${fp_cv_const_DT_WHT+y}
+then :
+  printf %s "(cached) " >&6
+else $as_nop
+  if ac_fn_c_compute_int "$LINENO" "DT_WHT" "fp_check_const_result"        "
+#if HAVE_STRUCT_DIRENT_D_TYPE
+#include <dirent.h>
+#endif
+"
+then :
+
+else $as_nop
+  fp_check_const_result=-9
+fi
+
+fp_cv_const_DT_WHT=$fp_check_const_result
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $fp_cv_const_DT_WHT" >&5
+printf "%s\n" "$fp_cv_const_DT_WHT" >&6; }
+printf "%s\n" "#define CONST_DT_WHT $fp_cv_const_DT_WHT" >>confdefs.h
 
 
 ac_fn_c_check_func "$LINENO" "getgrgid_r" "ac_cv_func_getgrgid_r"
diff --git a/configure.ac b/configure.ac
--- a/configure.ac
+++ b/configure.ac
@@ -27,6 +27,44 @@
 AC_CHECK_HEADERS([bsd/libutil.h libutil.h pty.h utmp.h])
 AC_CHECK_HEADERS([termios.h time.h unistd.h utime.h])
 
+AC_STRUCT_DIRENT_D_TYPE
+FP_CHECK_CONST([DT_UNKNOWN], [
+#if HAVE_STRUCT_DIRENT_D_TYPE
+#include <dirent.h>
+#endif], [-1])
+FP_CHECK_CONST([DT_FIFO], [
+#if HAVE_STRUCT_DIRENT_D_TYPE
+#include <dirent.h>
+#endif], [-2])
+FP_CHECK_CONST([DT_CHR], [
+#if HAVE_STRUCT_DIRENT_D_TYPE
+#include <dirent.h>
+#endif], [-3])
+FP_CHECK_CONST([DT_DIR], [
+#if HAVE_STRUCT_DIRENT_D_TYPE
+#include <dirent.h>
+#endif], [-4])
+FP_CHECK_CONST([DT_BLK], [
+#if HAVE_STRUCT_DIRENT_D_TYPE
+#include <dirent.h>
+#endif], [-5])
+FP_CHECK_CONST([DT_REG], [
+#if HAVE_STRUCT_DIRENT_D_TYPE
+#include <dirent.h>
+#endif], [-6])
+FP_CHECK_CONST([DT_LNK], [
+#if HAVE_STRUCT_DIRENT_D_TYPE
+#include <dirent.h>
+#endif], [-7])
+FP_CHECK_CONST([DT_SOCK], [
+#if HAVE_STRUCT_DIRENT_D_TYPE
+#include <dirent.h>
+#endif], [-8])
+FP_CHECK_CONST([DT_WHT], [
+#if HAVE_STRUCT_DIRENT_D_TYPE
+#include <dirent.h>
+#endif], [-9])
+
 AC_CHECK_FUNCS([getgrgid_r getgrnam_r getpwnam_r getpwuid_r getpwnam getpwuid])
 AC_CHECK_FUNCS([getpwent getgrent])
 AC_CHECK_FUNCS([lchown setenv sysconf unsetenv clearenv])
diff --git a/include/HsUnixConfig.h.in b/include/HsUnixConfig.h.in
--- a/include/HsUnixConfig.h.in
+++ b/include/HsUnixConfig.h.in
@@ -1,5 +1,32 @@
 /* include/HsUnixConfig.h.in.  Generated from configure.ac by autoheader.  */
 
+/* The value of DT_BLK. */
+#undef CONST_DT_BLK
+
+/* The value of DT_CHR. */
+#undef CONST_DT_CHR
+
+/* The value of DT_DIR. */
+#undef CONST_DT_DIR
+
+/* The value of DT_FIFO. */
+#undef CONST_DT_FIFO
+
+/* The value of DT_LNK. */
+#undef CONST_DT_LNK
+
+/* The value of DT_REG. */
+#undef CONST_DT_REG
+
+/* The value of DT_SOCK. */
+#undef CONST_DT_SOCK
+
+/* The value of DT_UNKNOWN. */
+#undef CONST_DT_UNKNOWN
+
+/* The value of DT_WHT. */
+#undef CONST_DT_WHT
+
 /* The value of SIGABRT. */
 #undef CONST_SIGABRT
 
@@ -147,7 +174,8 @@
 /* Define if we have /dev/ptmx. */
 #undef HAVE_DEV_PTMX
 
-/* Define to 1 if you have the <dirent.h> header file. */
+/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'.
+   */
 #undef HAVE_DIRENT_H
 
 /* Define to 1 if you have the `dup' function. */
@@ -246,6 +274,9 @@
 /* Define to 1 if you have the `nanosleep' function. */
 #undef HAVE_NANOSLEEP
 
+/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */
+#undef HAVE_NDIR_H
+
 /* Ignore the pw_gecos member of passwd where it does not exist */
 #undef HAVE_NO_PASSWD_PW_GECOS
 
@@ -327,6 +358,9 @@
 /* Define to 1 if you have the <string.h> header file. */
 #undef HAVE_STRING_H
 
+/* Define to 1 if `d_type' is a member of `struct dirent'. */
+#undef HAVE_STRUCT_DIRENT_D_TYPE
+
 /* HAVE_STRUCT_RLIMIT */
 #undef HAVE_STRUCT_RLIMIT
 
@@ -386,6 +420,14 @@
 
 /* Define to 1 if you have the `sysconf' function. */
 #undef HAVE_SYSCONF
+
+/* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'.
+   */
+#undef HAVE_SYS_DIR_H
+
+/* Define to 1 if you have the <sys/ndir.h> header file, and it defines `DIR'.
+   */
+#undef HAVE_SYS_NDIR_H
 
 /* Define to 1 if you have the <sys/resource.h> header file. */
 #undef HAVE_SYS_RESOURCE_H
diff --git a/tests/ReadDirStream.hs b/tests/ReadDirStream.hs
new file mode 100644
--- /dev/null
+++ b/tests/ReadDirStream.hs
@@ -0,0 +1,55 @@
+module ReadDirStream
+  ( emptyDirStream
+  , nonEmptyDirStream
+  ) where
+
+import System.Posix.Files
+import System.Posix.Directory
+import System.Posix.IO
+import Control.Exception as E
+import Test.Tasty.HUnit
+
+emptyDirStream :: IO ()
+emptyDirStream = do
+  cleanup
+  createDirectory dir ownerReadMode
+  dir_p <- openDirStream dir
+  entries <- readDirStreamEntries dir_p
+  closeDirStream dir_p
+  cleanup
+  entries @?= []
+  where
+    dir = "emptyDirStream"
+
+    cleanup = do
+      ignoreIOExceptions $ removeDirectory dir
+
+nonEmptyDirStream :: IO ()
+nonEmptyDirStream = do
+  cleanup
+  createDirectory dir ownerModes
+  _ <- createFile (dir ++ "/file") ownerReadMode
+  dir_p <- openDirStream dir
+  entries <- readDirStreamEntries dir_p
+  closeDirStream dir_p
+  cleanup
+  entries @?= ["file"]
+  where
+    dir = "nonEmptyDirStream"
+
+    cleanup = do
+      ignoreIOExceptions $ removeLink $ dir ++ "/file"
+      ignoreIOExceptions $ removeDirectory dir
+
+readDirStreamEntries :: DirStream -> IO [FilePath]
+readDirStreamEntries dir_p = do
+  ment <- readDirStreamMaybe dir_p
+  case ment of
+    Nothing -> return []
+    Just "." -> readDirStreamEntries dir_p
+    Just ".." -> readDirStreamEntries dir_p
+    Just ent -> (ent :) <$> readDirStreamEntries dir_p
+
+ignoreIOExceptions :: IO () -> IO ()
+ignoreIOExceptions io = io `E.catch`
+                        ((\_ -> return ()) :: E.IOException -> IO ())
diff --git a/tests/Test.hsc b/tests/Test.hsc
--- a/tests/Test.hsc
+++ b/tests/Test.hsc
@@ -29,6 +29,7 @@
 import qualified FileStatus
 import qualified FileExtendedStatus
 import qualified FileStatusByteString
+import qualified ReadDirStream
 import qualified Signals001
 
 main :: IO ()
@@ -59,6 +60,8 @@
     , posix005                     -- JS: missing "environ"
     , posix006                     -- JS: missing "time"
     , posix010                     -- JS: missing "sysconf"
+    , emptyDirStream
+    , nonEmptyDirStream
     ]
 #endif
   , testWithFilePath
@@ -274,6 +277,12 @@
           ioProperty $ PPFP.withFilePath (PosixString ys)
             (\ptr -> (=== ys) <$> Sh.packCString ptr)
       ]
+
+emptyDirStream :: TestTree
+emptyDirStream = testCase "emptyDirStream" ReadDirStream.emptyDirStream
+
+nonEmptyDirStream :: TestTree
+nonEmptyDirStream = testCase "nonEmptyDirStream" ReadDirStream.nonEmptyDirStream
 
 -------------------------------------------------------------------------------
 -- Utils
diff --git a/unix.cabal b/unix.cabal
--- a/unix.cabal
+++ b/unix.cabal
@@ -1,6 +1,6 @@
 cabal-version:  1.12
 name:           unix
-version:        2.8.5.1
+version:        2.8.6.0
 -- NOTE: Don't forget to update ./changelog.md
 
 license:        BSD3
@@ -155,9 +155,6 @@
     ghc-options: -Wall
 
     include-dirs: include
-    includes:
-        HsUnix.h
-        execvpe.h
     install-includes:
         HsUnix.h
         execvpe.h
@@ -179,6 +176,7 @@
         FileExtendedStatus
         FileStatusByteString
         Signals001
+        ReadDirStream
     type: exitcode-stdio-1.0
     default-language: Haskell2010
     build-depends: base, bytestring, tasty, tasty-hunit, tasty-quickcheck, unix
