    {-# OPTIONS_GHC -XNoImplicitPrelude #-}
    -----------------------------------------------------------------------------
    -- |
    -- Module      :  GHC.IO.Handle.FD
    -- Copyright   :  (c) The University of Glasgow, 1994-2008
    -- License     :  see libraries/base/LICENSE
    --
    -- Maintainer  :  libraries@haskell.org
    -- Stability   :  internal
    -- Portability :  non-portable
    --
    -- Handle operations implemented by file descriptors (FDs)
    --
    -----------------------------------------------------------------------------

    module GHC.IO.Handle.FD (
      stdin, stdout, stderr,
      openFile, openBinaryFile,
      mkHandleFromFD, fdToHandle, fdToHandle',
      isEOF
     ) where

    import GHC.Base
    import GHC.Show
    import Data.Maybe
    -- import Control.Monad
    import Foreign.C.Types
    import GHC.MVar
    import GHC.IO
    import GHC.IO.Encoding
    -- import GHC.IO.Exception
    import GHC.IO.Device as IODevice
    import GHC.IO.Exception
    import GHC.IO.IOMode
    import GHC.IO.Handle
    import GHC.IO.Handle.Types
    import GHC.IO.Handle.Internals
    import GHC.IO.FD (FD(..))
    import qualified GHC.IO.FD as FD
    import qualified System.Posix.Internals as Posix

    -- ---------------------------------------------------------------------------
    -- Standard Handles

    -- Three handles are allocated during program initialisation.  The first
    -- two manage input or output from the Haskell program's standard input
    -- or output channel respectively.  The third manages output to the
    -- standard error channel. These handles are initially open.

    -- | A handle managing input from the Haskell program's standard input channel.
    stdin :: Handle
    {-# NOINLINE stdin #-}
    stdin = unsafePerformIO $ do
       -- ToDo: acquire lock
       setBinaryMode FD.stdin
       mkHandle FD.stdin "<stdin>" ReadHandle True (Just localeEncoding)
                    nativeNewlineMode{-translate newlines-}
                    (Just stdHandleFinalizer) Nothing

    -- | A handle managing output to the Haskell program's standard output channel.
    stdout :: Handle
    {-# NOINLINE stdout #-}
    stdout = unsafePerformIO $ do
       -- ToDo: acquire lock
       setBinaryMode FD.stdout
       mkHandle FD.stdout "<stdout>" WriteHandle True (Just localeEncoding)
                    nativeNewlineMode{-translate newlines-}
                    (Just stdHandleFinalizer) Nothing

    -- | A handle managing output to the Haskell program's standard error channel.
    stderr :: Handle
    {-# NOINLINE stderr #-}
    stderr = unsafePerformIO $ do
        -- ToDo: acquire lock
       setBinaryMode FD.stderr
       mkHandle FD.stderr "<stderr>" WriteHandle False{-stderr is unbuffered-}
                    (Just localeEncoding)
                    nativeNewlineMode{-translate newlines-}
                    (Just stdHandleFinalizer) Nothing

    stdHandleFinalizer :: FilePath -> MVar Handle__ -> IO ()
    stdHandleFinalizer fp m = do
      h_ <- takeMVar m
      flushWriteBuffer h_
      case haType h_ of
          ClosedHandle -> return ()
          _other       -> closeTextCodecs h_
      putMVar m (ioe_finalizedHandle fp)

    -- We have to put the FDs into binary mode on Windows to avoid the newline
    -- translation that the CRT IO library does.
    setBinaryMode :: FD -> IO ()
    #ifdef mingw32_HOST_OS
    setBinaryMode fd = do _ <- setmode (fdFD fd) True
                          return ()
    #else
    setBinaryMode _ = return ()
    #endif

    #ifdef mingw32_HOST_OS
    foreign import ccall unsafe "__hscore_setmode"
      setmode :: CInt -> Bool -> IO CInt
    #endif

    -- ---------------------------------------------------------------------------
    -- isEOF

    -- | The computation 'isEOF' is identical to 'hIsEOF',
    -- except that it works only on 'stdin'.

    isEOF :: IO Bool
    isEOF = hIsEOF stdin

    -- ---------------------------------------------------------------------------
    -- Opening and Closing Files

    addFilePathToIOError :: String -> FilePath -> IOException -> IOException
    addFilePathToIOError fun fp ioe
      = ioe{ ioe_location = fun, ioe_filename = Just fp }

    -- | Computation 'openFile' @file mode@ allocates and returns a new, open
    -- handle to manage the file @file@.  It manages input if @mode@
    -- is 'ReadMode', output if @mode@ is 'WriteMode' or 'AppendMode',
    -- and both input and output if mode is 'ReadWriteMode'.
    --
    -- If the file does not exist and it is opened for output, it should be
    -- created as a new file.  If @mode@ is 'WriteMode' and the file
    -- already exists, then it should be truncated to zero length.
    -- Some operating systems delete empty files, so there is no guarantee
    -- that the file will exist following an 'openFile' with @mode@
    -- 'WriteMode' unless it is subsequently written to successfully.
    -- The handle is positioned at the end of the file if @mode@ is
    -- 'AppendMode', and otherwise at the beginning (in which case its
    -- internal position is 0).
    -- The initial buffer mode is implementation-dependent.
    --
    -- This operation may fail with:
    --
    --  * 'isAlreadyInUseError' if the file is already open and cannot be reopened;
    --
    --  * 'isDoesNotExistError' if the file does not exist; or
    --
    --  * 'isPermissionError' if the user does not have permission to open the file.
    --
    -- Note: if you will be working with files containing binary data, you'll want to
    -- be using 'openBinaryFile'.
    openFile :: FilePath -> IOMode -> IO Handle
    openFile fp im =
      catchException
        (openFile' fp im dEFAULT_OPEN_IN_BINARY_MODE)
        (\e -> ioError (addFilePathToIOError "openFile" fp e))

    -- | Like 'openFile', but open the file in binary mode.
    -- On Windows, reading a file in text mode (which is the default)
    -- will translate CRLF to LF, and writing will translate LF to CRLF.
    -- This is usually what you want with text files.  With binary files
    -- this is undesirable; also, as usual under Microsoft operating systems,
    -- text mode treats control-Z as EOF.  Binary mode turns off all special
    -- treatment of end-of-line and end-of-file characters.
    -- (See also 'hSetBinaryMode'.)

    openBinaryFile :: FilePath -> IOMode -> IO Handle
    openBinaryFile fp m =
      catchException
        (openFile' fp m True)
        (\e -> ioError (addFilePathToIOError "openBinaryFile" fp e))

    openFile' :: String -> IOMode -> Bool -> IO Handle
    openFile' filepath iomode binary = do
      -- first open the file to get an FD
      (fd, fd_type) <- FD.openFile filepath iomode

      let mb_codec = if binary then Nothing else Just localeEncoding

      -- then use it to make a Handle
      mkHandleFromFD fd fd_type filepath iomode True{-non-blocking-} mb_codec
                `onException` IODevice.close fd
            -- NB. don't forget to close the FD if mkHandleFromFD fails, otherwise
            -- this FD leaks.
            -- ASSERT: if we just created the file, then fdToHandle' won't fail
            -- (so we don't need to worry about removing the newly created file
            --  in the event of an error).

    -- ---------------------------------------------------------------------------
    -- Converting file descriptors to Handles

    mkHandleFromFD
       :: FD
       -> IODeviceType
       -> FilePath -- a string describing this file descriptor (e.g. the filename)
       -> IOMode
       -> Bool -- non_blocking (*sets* non-blocking mode on the FD)
       -> Maybe TextEncoding
       -> IO Handle

    mkHandleFromFD fd0 fd_type filepath iomode set_non_blocking mb_codec
      = do
    #ifndef mingw32_HOST_OS
        -- turn on non-blocking mode
        fd <- if set_non_blocking
                 then FD.setNonBlockingMode fd0 True
                 else return fd0
    #else
        let _ = set_non_blocking -- warning suppression
        fd <- return fd0
    #endif

        let nl | isJust mb_codec = nativeNewlineMode
               | otherwise       = noNewlineTranslation

        case fd_type of
            Directory ->
               ioException (IOError Nothing InappropriateType "openFile"
                               "is a directory" Nothing Nothing)

            Stream
               -- only *Streams* can be DuplexHandles.  Other read/write
               -- Handles must share a buffer.
               | ReadWriteMode <- iomode ->
                    mkDuplexHandle fd filepath mb_codec nl

            _other ->
               mkFileHandle fd filepath iomode mb_codec nl

    -- | Old API kept to avoid breaking clients
    fdToHandle' :: CInt
                -> Maybe IODeviceType
                -> Bool -- is_socket on Win, non-blocking on Unix
                -> FilePath
                -> IOMode
                -> Bool -- binary
                -> IO Handle
    fdToHandle' fdint mb_type is_socket filepath iomode binary = do
      let mb_stat = case mb_type of
                            Nothing          -> Nothing
                              -- mkFD will do the stat:
                            Just RegularFile -> Nothing
                              -- no stat required for streams etc.:
                            Just other       -> Just (other,0,0)
      (fd,fd_type) <- FD.mkFD fdint iomode mb_stat
                           is_socket
                           is_socket
      mkHandleFromFD fd fd_type filepath iomode is_socket
                           (if binary then Nothing else Just localeEncoding)

    -- | Turn an existing file descriptor into a Handle.  This is used by
    -- various external libraries to make Handles.
    --
    -- Makes a binary Handle.  This is for historical reasons; it should
    -- probably be a text Handle with the default encoding and newline
    -- translation instead.
    fdToHandle :: Posix.FD -> IO Handle
    fdToHandle fdint = do
       iomode <- Posix.fdGetMode fdint
       (fd,fd_type) <- FD.mkFD fdint iomode Nothing
                False{-is_socket-}
                  -- NB. the is_socket flag is False, meaning that:
                  --  on Windows we're guessing this is not a socket (XXX)
                False{-is_nonblock-}
                  -- file descriptors that we get from external sources are
                  -- not put into non-blocking mode, becuase that would affect
                  -- other users of the file descriptor
       let fd_str = "<file descriptor: " ++ show fd ++ ">"
       mkHandleFromFD fd fd_type fd_str iomode False{-non-block-}
                      Nothing -- bin mode

    -- ---------------------------------------------------------------------------
    -- Are files opened by default in text or binary mode, if the user doesn't
    -- specify?

    dEFAULT_OPEN_IN_BINARY_MODE :: Bool
    dEFAULT_OPEN_IN_BINARY_MODE = False