    {-# LANGUAGE CPP              #-}
    {-# LANGUAGE TupleSections    #-}
    {-# LANGUAGE TypeApplications #-}
    {-# LANGUAGE PackageImports   #-}

    module System.File.Platform where

    import Data.Either (fromRight)
    import Control.Exception (try, onException, SomeException)
    import GHC.IO.Handle.FD (fdToHandle')
    import System.IO (IOMode(..), Handle)
    import System.Posix.Types (Fd(..))
    import System.Posix.IO.PosixString
        ( defaultFileFlags,
          openFd,
          closeFd,
          OpenFileFlags(noctty, nonBlock, creat, append, trunc, cloexec, exclusive),
          OpenMode(ReadWrite, ReadOnly, WriteOnly) )
    import System.OsPath.Posix ( PosixPath, PosixString, (</>) )
    import qualified System.OsPath.Posix as PS
    import Data.IORef (IORef, newIORef)
    import System.Posix (CMode)
    import System.IO (utf8, latin1)
    import System.IO.Unsafe (unsafePerformIO)
    import System.Posix.Internals (c_getpid)
    import GHC.IORef (atomicModifyIORef'_)
    import Foreign.C (getErrno, eEXIST, errnoToIOError)

    #if !MIN_VERSION_filepath(1, 5, 0)
    import Data.Coerce (coerce)
    import "filepath" System.OsString.Internal.Types (PosixString(..), PosixChar(..))
    import qualified "filepath" System.OsPath.Data.ByteString.Short as BC
    #endif
    import System.CPUTime (cpuTimePrecision, getCPUTime)
    import Text.Printf (printf)

    -- | Open a file and return the 'Handle'.
    openFile :: PosixPath -> IOMode -> IO Handle
    openFile :: PosixPath -> IOMode -> IO Handle
    openFile = OpenFileFlags -> PosixPath -> IOMode -> IO Handle
    openFile_ OpenFileFlags
    defaultFileFlags'

    openFile_ :: OpenFileFlags -> PosixPath -> IOMode -> IO Handle
    openFile_ :: OpenFileFlags -> PosixPath -> IOMode -> IO Handle
    openFile_ OpenFileFlags
    df PosixPath
    fp IOMode
    iomode = IOMode -> PosixPath -> Fd -> IO Handle
    fdToHandle_ IOMode
    iomode PosixPath
    fp (Fd -> IO Handle) -> IO Fd -> IO Handle
    forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
    =<< case IOMode
    iomode of
      IOMode
    ReadMode      -> OpenMode -> OpenFileFlags -> IO Fd
    open OpenMode
    ReadOnly  OpenFileFlags
    df
      IOMode
    WriteMode     -> OpenMode -> OpenFileFlags -> IO Fd
    open OpenMode
    WriteOnly OpenFileFlags
    df { trunc = True, creat = Just 0o666 }
      IOMode
    AppendMode    -> OpenMode -> OpenFileFlags -> IO Fd
    open OpenMode
    WriteOnly OpenFileFlags
    df { append = True, creat = Just 0o666 }
      IOMode
    ReadWriteMode -> OpenMode -> OpenFileFlags -> IO Fd
    open OpenMode
    ReadWrite OpenFileFlags
    df { creat = Just 0o666 }
     where
      open :: OpenMode -> OpenFileFlags -> IO Fd
    open = PosixPath -> OpenMode -> OpenFileFlags -> IO Fd
    openFd PosixPath
    fp

    -- | Open an existing file and return the 'Handle'.
    openExistingFile :: PosixPath -> IOMode -> IO Handle
    openExistingFile :: PosixPath -> IOMode -> IO Handle
    openExistingFile = OpenFileFlags -> PosixPath -> IOMode -> IO Handle
    openExistingFile_ OpenFileFlags
    defaultExistingFileFlags

    openExistingFile_ :: OpenFileFlags -> PosixPath -> IOMode -> IO Handle
    openExistingFile_ :: OpenFileFlags -> PosixPath -> IOMode -> IO Handle
    openExistingFile_ OpenFileFlags
    df PosixPath
    fp IOMode
    iomode = IOMode -> PosixPath -> Fd -> IO Handle
    fdToHandle_ IOMode
    iomode PosixPath
    fp (Fd -> IO Handle) -> IO Fd -> IO Handle
    forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
    =<< case IOMode
    iomode of
      IOMode
    ReadMode      -> OpenMode -> OpenFileFlags -> IO Fd
    open OpenMode
    ReadOnly  OpenFileFlags
    df
      IOMode
    WriteMode     -> OpenMode -> OpenFileFlags -> IO Fd
    open OpenMode
    WriteOnly OpenFileFlags
    df { trunc = True }
      IOMode
    AppendMode    -> OpenMode -> OpenFileFlags -> IO Fd
    open OpenMode
    WriteOnly OpenFileFlags
    df { append = True }
      IOMode
    ReadWriteMode -> OpenMode -> OpenFileFlags -> IO Fd
    open OpenMode
    ReadWrite OpenFileFlags
    df
     where
      open :: OpenMode -> OpenFileFlags -> IO Fd
    open = PosixPath -> OpenMode -> OpenFileFlags -> IO Fd
    openFd PosixPath
    fp

    fdToHandle_ :: IOMode -> PosixPath -> Fd -> IO Handle
    fdToHandle_ :: IOMode -> PosixPath -> Fd -> IO Handle
    fdToHandle_ IOMode
    iomode PosixPath
    fp (Fd CInt
    fd) = (IO Handle -> IO () -> IO Handle
    forall a b. IO a -> IO b -> IO a
    `onException` Fd -> IO ()
    closeFd (CInt -> Fd
    Fd CInt
    fd)) (IO Handle -> IO Handle) -> IO Handle -> IO Handle
    forall a b. (a -> b) -> a -> b
    $ do
        fp'  <- [Char] -> Either SomeException [Char] -> [Char]
    forall b a. b -> Either a b -> b
    fromRight ((PosixChar -> Char) -> [PosixChar] -> [Char]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap PosixChar -> Char
    PS.toChar ([PosixChar] -> [Char])
    -> (PosixPath -> [PosixChar]) -> PosixPath -> [Char]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . PosixPath -> [PosixChar]
    PS.unpack (PosixPath -> [Char]) -> PosixPath -> [Char]
    forall a b. (a -> b) -> a -> b
    $ PosixPath
    fp) (Either SomeException [Char] -> [Char])
    -> IO (Either SomeException [Char]) -> IO [Char]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> forall e a. Exception e => IO a -> IO (Either e a)
    try @SomeException (PosixPath -> IO [Char]
    PS.decodeFS PosixPath
    fp)
        fdToHandle' fd Nothing False fp' iomode True

    openFileWithCloseOnExec :: PosixPath -> IOMode -> IO Handle
    openFileWithCloseOnExec :: PosixPath -> IOMode -> IO Handle
    openFileWithCloseOnExec = OpenFileFlags -> PosixPath -> IOMode -> IO Handle
    openFile_ OpenFileFlags
    defaultFileFlags' { cloexec = True }

    openExistingFileWithCloseOnExec :: PosixPath -> IOMode -> IO Handle
    openExistingFileWithCloseOnExec :: PosixPath -> IOMode -> IO Handle
    openExistingFileWithCloseOnExec = OpenFileFlags -> PosixPath -> IOMode -> IO Handle
    openExistingFile_ OpenFileFlags
    defaultExistingFileFlags { cloexec = True }

    defaultFileFlags' :: OpenFileFlags
    defaultFileFlags' :: OpenFileFlags
    defaultFileFlags' = OpenFileFlags
    defaultFileFlags { noctty = True, nonBlock = True }

    defaultExistingFileFlags :: OpenFileFlags
    defaultExistingFileFlags :: OpenFileFlags
    defaultExistingFileFlags = OpenFileFlags
    defaultFileFlags { noctty = True, nonBlock = True, creat = Nothing }

    findTempName :: (PosixString, PosixString)
                 -> String
                 -> PosixPath
                 -> CMode
                 -> IO (PosixPath, Handle)
    findTempName :: (PosixPath, PosixPath)
    -> [Char] -> PosixPath -> FileMode -> IO (PosixPath, Handle)
    findTempName (PosixPath
    prefix, PosixPath
    suffix) [Char]
    loc PosixPath
    tmp_dir FileMode
    mode = IO (PosixPath, Handle)
    go
     where
      go :: IO (PosixPath, Handle)
    go = do
        rs <- IO PosixPath
    rand_string
        let filename = PosixPath
    prefix PosixPath -> PosixPath -> PosixPath
    forall a. Semigroup a => a -> a -> a
    <> PosixPath
    rs PosixPath -> PosixPath -> PosixPath
    forall a. Semigroup a => a -> a -> a
    <> PosixPath
    suffix
            filepath = PosixPath
    tmp_dir PosixPath -> PosixPath -> PosixPath
    </> PosixPath
    filename
        fd <- openTempFile_ filepath mode
        if fd < 0
        then do
          errno <- getErrno
          case errno of
              Errno
    _ | Errno
    errno Errno -> Errno -> Bool
    forall a. Eq a => a -> a -> Bool
    == Errno
    eEXIST -> IO (PosixPath, Handle)
    go
              Errno
    _ -> do
                let tmp_dir' :: [Char]
    tmp_dir' = PosixPath -> [Char]
    lenientDecode PosixPath
    tmp_dir
                IOError -> IO (PosixPath, Handle)
    forall a. IOError -> IO a
    ioError ([Char] -> Errno -> Maybe Handle -> Maybe [Char] -> IOError
    errnoToIOError [Char]
    loc Errno
    errno Maybe Handle
    forall a. Maybe a
    Nothing ([Char] -> Maybe [Char]
    forall a. a -> Maybe a
    Just [Char]
    tmp_dir'))
        else fmap (filepath,) $ fdToHandle_ ReadWriteMode filepath fd

      openTempFile_ :: PosixPath -> CMode -> IO Fd
      openTempFile_ :: PosixPath -> FileMode -> IO Fd
    openTempFile_ PosixPath
    fp FileMode
    cmode = PosixPath -> OpenMode -> OpenFileFlags -> IO Fd
    openFd PosixPath
    fp OpenMode
    ReadWrite OpenFileFlags
    defaultFileFlags' { creat = Just cmode, nonBlock = True, noctty = True, exclusive = True }

    tempCounter :: IORef Int
    tempCounter :: IORef Int
    tempCounter = IO (IORef Int) -> IORef Int
    forall a. IO a -> a
    unsafePerformIO (IO (IORef Int) -> IORef Int) -> IO (IORef Int) -> IORef Int
    forall a b. (a -> b) -> a -> b
    $ Int -> IO (IORef Int)
    forall a. a -> IO (IORef a)
    newIORef Int
    0
    {-# NOINLINE tempCounter #-}

    -- build large digit-alike number
    rand_string :: IO PosixString
    rand_string :: IO PosixPath
    rand_string = do
      r1 <- forall a b. (Integral a, Num b) => a -> b
    fromIntegral @_ @Int (CPid -> Int) -> IO CPid -> IO Int
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> IO CPid
    c_getpid
      (r2, _) <- atomicModifyIORef'_ tempCounter (+1)
      r3 <- (`quot` cpuTimePrecision) <$> getCPUTime
      return $ PS.pack $ fmap (PS.unsafeFromChar) (printf "%x-%x-%x" r1 r2 r3)

    lenientDecode :: PosixString -> String
    lenientDecode :: PosixPath -> [Char]
    lenientDecode PosixPath
    ps = let utf8' :: Either EncodingException [Char]
    utf8' = TextEncoding -> PosixPath -> Either EncodingException [Char]
    PS.decodeWith TextEncoding
    utf8 PosixPath
    ps
                           latin1' :: Either EncodingException [Char]
    latin1' = TextEncoding -> PosixPath -> Either EncodingException [Char]
    PS.decodeWith TextEncoding
    latin1 PosixPath
    ps
                       in case (Either EncodingException [Char]
    utf8', Either EncodingException [Char]
    latin1') of
                            (Right [Char]
    s, ~Either EncodingException [Char]
    _) -> [Char]
    s
                            (Either EncodingException [Char]
    _, Right [Char]
    s) -> [Char]
    s
                            (Left EncodingException
    _, Left EncodingException
    _) -> [Char] -> [Char]
    forall a. HasCallStack => [Char] -> a
    error [Char]
    "lenientDecode: failed to decode"

    #if !MIN_VERSION_filepath(1, 5, 0)

    any_ :: (PosixChar -> Bool) -> PosixString -> Bool
    any_ = coerce BC.any

    #endif