    {-# LINE 1 "libraries/unix/System/Posix/Directory/Common.hsc" #-}
    {-# LANGUAGE CPP, Safe, CApiFFI, MultiWayIf, PatternSynonyms #-}

    -----------------------------------------------------------------------------
    -- |
    -- Module      :  System.Posix.Directory.Common
    -- Copyright   :  (c) The University of Glasgow 2002
    -- License     :  BSD-style (see the file libraries/base/LICENSE)
    --
    -- Maintainer  :  libraries@haskell.org
    -- Stability   :  provisional
    -- Portability :  non-portable (requires POSIX)
    --
    -- POSIX directory support
    --
    -----------------------------------------------------------------------------

    #include "HsUnixConfig.h"

    module System.Posix.Directory.Common (
           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,

    {-# LINE 61 "libraries/unix/System/Posix/Directory/Common.hsc" #-}
           seekDirStream,

    {-# LINE 63 "libraries/unix/System/Posix/Directory/Common.hsc" #-}

    {-# LINE 64 "libraries/unix/System/Posix/Directory/Common.hsc" #-}
           tellDirStream,

    {-# LINE 66 "libraries/unix/System/Posix/Directory/Common.hsc" #-}
           changeWorkingDirectoryFd,
      ) where

    import Control.Exception (mask_)
    import Control.Monad (void, when)
    import System.Posix.Types
    import Foreign hiding (void)
    import Foreign.C

    {-# LINE 79 "libraries/unix/System/Posix/Directory/Common.hsc" #-}

    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.
    --
    -- On success, it is owned by the returned 'DirStream', which should be closed
    -- via 'closeDirStream' when no longer needed.  On error, the file descriptor
    -- is automatically closed and then an exception is thrown.  There is no code
    -- path in which the file descriptor remains open and yet not owned by a
    -- returned 'DirStream'.
    --
    -- 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
        when (ptr == nullPtr) $ do
            errno <- getErrno
            void $ c_close fd
            ioError (errnoToIOError "openDirStreamFd" errno Nothing Nothing)
        return $ DirStream ptr

    -- We need c_close here, because 'closeFd' throws exceptions on error,
    -- but we want to silently close the (presumably directory) descriptor.
    foreign import ccall unsafe "HsUnix.h close"
       c_close :: CInt -> IO CInt

    -- NOTE: It is /critical/ to use "capi" and "dirent.h" here, because system
    -- headers on e.g. macOS alias this function, and linking directly to the
    -- "fdopendir" symbol in libc leads to a crash!
    --
    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.
    rewindDirStream :: DirStream -> IO ()
    rewindDirStream (DirStream dirp) = c_rewinddir dirp

    foreign import ccall unsafe "rewinddir"
       c_rewinddir :: Ptr CDir -> IO ()

    -- | @closeDirStream dp@ calls @closedir@ to close
    --   the directory stream @dp@.
    closeDirStream :: DirStream -> IO ()
    closeDirStream (DirStream dirp) = do
      throwErrnoIfMinus1Retry_ "closeDirStream" (c_closedir dirp)

    foreign import ccall unsafe "closedir"
       c_closedir :: Ptr CDir -> IO CInt

    newtype DirStreamOffset = DirStreamOffset COff

    {-# LINE 375 "libraries/unix/System/Posix/Directory/Common.hsc" #-}
    seekDirStream :: DirStream -> DirStreamOffset -> IO ()
    seekDirStream (DirStream dirp) (DirStreamOffset off) =
      c_seekdir dirp (fromIntegral off) -- TODO: check for CLong/COff overflow

    foreign import ccall unsafe "seekdir"
      c_seekdir :: Ptr CDir -> CLong -> IO ()

    {-# LINE 382 "libraries/unix/System/Posix/Directory/Common.hsc" #-}

    {-# LINE 384 "libraries/unix/System/Posix/Directory/Common.hsc" #-}
    tellDirStream :: DirStream -> IO DirStreamOffset
    tellDirStream (DirStream dirp) = do
      off <- c_telldir dirp
      return (DirStreamOffset (fromIntegral off)) -- TODO: check for overflow

    foreign import ccall unsafe "telldir"
      c_telldir :: Ptr CDir -> IO CLong

    {-# LINE 392 "libraries/unix/System/Posix/Directory/Common.hsc" #-}

    {-# LINE 394 "libraries/unix/System/Posix/Directory/Common.hsc" #-}

    changeWorkingDirectoryFd :: Fd -> IO ()
    changeWorkingDirectoryFd (Fd fd) =
      throwErrnoIfMinus1Retry_ "changeWorkingDirectoryFd" (c_fchdir fd)

    foreign import ccall unsafe "fchdir"
      c_fchdir :: CInt -> IO CInt

    {-# LINE 409 "libraries/unix/System/Posix/Directory/Common.hsc" #-}