diff --git a/System/Posix/Directory.hsc b/System/Posix/Directory.hsc
index 0f856c45..5f01c15c 100644
--- a/System/Posix/Directory.hsc
+++ b/System/Posix/Directory.hsc
@@ -48,6 +48,7 @@ module System.Posix.Directory (
    changeWorkingDirectoryFd,
   ) where
 
+import Control.Monad ((>=>))
 import Data.Maybe
 import System.Posix.Error
 import System.Posix.Types
@@ -84,50 +85,21 @@ foreign import capi unsafe "HsUnix.h opendir"
 -- | @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
index 3d6bbea5..be5cb878 100644
--- a/System/Posix/Directory/ByteString.hsc
+++ b/System/Posix/Directory/ByteString.hsc
@@ -48,6 +48,7 @@ module System.Posix.Directory.ByteString (
    changeWorkingDirectoryFd,
   ) where
 
+import Control.Monad ((>=>))
 import Data.Maybe
 import System.Posix.Types
 import Foreign
@@ -60,7 +61,7 @@ import System.Posix.ByteString.FilePath
 
 -- | @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 @@ foreign import capi unsafe "HsUnix.h opendir"
 -- | @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
index e4aa7656..55dc28d5 100644
--- 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,40 @@
 -----------------------------------------------------------------------------
 
 #include "HsUnix.h"
+#include "HsUnixConfig.h"
+##include "HsUnixConfig.h"
 
 module System.Posix.Directory.Common (
-       DirStream(..), CDir, CDirent, DirStreamOffset(..),
+       DirStream(..),
+       CDir,
+       DirStreamWithPath(..),
+       fromDirStreamWithPath,
+       toDirStreamWithPath,
+
+       DirEnt(..),
+       CDirent,
+       dirEntName,
+       dirEntType,
+       DirType( DirType
+              , UnknownType
+              , NamedPipeType
+              , CharacterDeviceType
+              , DirectoryType
+              , BlockDeviceType
+              , RegularFileType
+              , SymbolicLinkType
+              , SocketType
+              , WhiteoutType
+              ),
+       isUnknownType, isBlockDeviceType, isCharacterDeviceType, isNamedPipeType,
+       isRegularFileType, isDirectoryType, isSymbolicLinkType, isSocketType,
+       isWhiteoutType,
+       getRealDirType,
        unsafeOpenDirStreamFd,
+       readDirStreamWith,
+       readDirStreamWithPtr,
+
+       DirStreamOffset(..),
        rewindDirStream,
        closeDirStream,
 #ifdef HAVE_SEEKDIR
@@ -41,11 +71,149 @@ import System.IO.Error ( ioeSetLocation )
 import GHC.IO.Exception ( unsupportedOperation )
 #endif
 
+import System.Posix.Files.Common
+
 newtype DirStream = DirStream (Ptr CDir)
 
+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'.
+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'.
+toDirStreamWithPath :: a -> DirStream -> DirStreamWithPath a
+toDirStreamWithPath path (DirStream ptr) = DirStreamWithPath (path, ptr)
+
+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.
+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.
+isUnknownType         :: DirType -> Bool
+-- | Checks if this 'DirType' refers to a block device entry.
+isBlockDeviceType     :: DirType -> Bool
+-- | Checks if this 'DirType' refers to a character device entry.
+isCharacterDeviceType :: DirType -> Bool
+-- | Checks if this 'DirType' refers to a named pipe entry.
+isNamedPipeType       :: DirType -> Bool
+-- | Checks if this 'DirType' refers to a regular file entry.
+isRegularFileType     :: DirType -> Bool
+-- | Checks if this 'DirType' refers to a directory entry.
+isDirectoryType       :: DirType -> Bool
+-- | Checks if this 'DirType' refers to a symbolic link entry.
+isSymbolicLinkType    :: DirType -> Bool
+-- | Checks if this 'DirType' refers to a socket entry.
+isSocketType          :: DirType -> Bool
+-- | Checks if this 'DirType' refers to a whiteout entry.
+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
+
+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.
 --
@@ -78,6 +246,71 @@ foreign import ccall unsafe "HsUnix.h close"
 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!
+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.
+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"
+
+dirEntName :: DirEnt -> IO CString
+dirEntName (DirEnt dEntPtr) = d_name dEntPtr
+
+foreign import ccall unsafe "__hscore_d_name"
+  d_name :: Ptr CDirent -> IO CString
+
+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.
 rewindDirStream :: DirStream -> IO ()
diff --git a/System/Posix/Directory/Internals.hsc b/System/Posix/Directory/Internals.hsc
index 6a7dae62..378a087a 100644
--- a/System/Posix/Directory/Internals.hsc
+++ b/System/Posix/Directory/Internals.hsc
@@ -12,6 +12,40 @@
 --
 -----------------------------------------------------------------------------
 
-module System.Posix.Directory.Internals ( DirStream(..), CDir, CDirent, DirStreamOffset(..) ) where
+module System.Posix.Directory.Internals (
+    DirStream(..),
+    CDir,
+    DirStreamWithPath(..),
+    fromDirStreamWithPath,
+    toDirStreamWithPath,
+    DirEnt(..),
+    CDirent,
+    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,
+    DirStreamOffset(..),
+    ) where
 
 import System.Posix.Directory.Common
diff --git a/System/Posix/Directory/PosixPath.hsc b/System/Posix/Directory/PosixPath.hsc
index 3d3ad612..24570c59 100644
--- a/System/Posix/Directory/PosixPath.hsc
+++ b/System/Posix/Directory/PosixPath.hsc
@@ -27,37 +27,39 @@ module System.Posix.Directory.PosixPath (
    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 @@ foreign import ccall unsafe "mkdir"
 
 -- | @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 @@ foreign import capi unsafe "HsUnix.h opendir"
 -- | @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 ()
-
-foreign import ccall unsafe "__hscore_d_name"
-  d_name :: Ptr Common.CDirent -> IO CString
+--   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
+
+-- | @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/cbits/HsUnix.c b/cbits/HsUnix.c
index 60d259f3..d2d890ef 100644
--- a/cbits/HsUnix.c
+++ b/cbits/HsUnix.c
@@ -104,6 +104,15 @@ char *__hscore_d_name( struct dirent* d )
   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/configure.ac b/configure.ac
index 2f36f524..dcd88d34 100644
--- a/configure.ac
+++ b/configure.ac
@@ -27,6 +27,44 @@ AC_CHECK_HEADERS([sys/sysmacros.h])
 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/tests/ReadDirStream.hs b/tests/ReadDirStream.hs
new file mode 100644
index 00000000..9ba11e5c
--- /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
index f6771cf3..ca85a9b9 100644
--- a/tests/Test.hsc
+++ b/tests/Test.hsc
@@ -29,6 +29,7 @@ import Test.Tasty.QuickCheck
 import qualified FileStatus
 import qualified FileExtendedStatus
 import qualified FileStatusByteString
+import qualified ReadDirStream
 import qualified Signals001
 
 main :: IO ()
@@ -59,6 +60,8 @@ main = defaultMain $ testGroup "All"
     , posix005                     -- JS: missing "environ"
     , posix006                     -- JS: missing "time"
     , posix010                     -- JS: missing "sysconf"
+    , emptyDirStream
+    , nonEmptyDirStream
     ]
 #endif
   , testWithFilePath
@@ -275,6 +278,12 @@ testWithFilePath =
             (\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
index 1f37c908..584eac66 100644
--- a/unix.cabal
+++ b/unix.cabal
@@ -179,6 +179,7 @@ test-suite unix-tests
         FileExtendedStatus
         FileStatusByteString
         Signals001
+        ReadDirStream
     type: exitcode-stdio-1.0
     default-language: Haskell2010
     build-depends: base, bytestring, tasty, tasty-hunit, tasty-quickcheck, unix