-{-# OPTIONS -fno-implicit-prelude #-}
+{-# OPTIONS_GHC -XNoImplicitPrelude #-}
-----------------------------------------------------------------------------
-- |
-- Module : System.IO
module System.IO (
-- * The IO monad
- IO, -- instance MonadFix
- fixIO, -- :: (a -> IO a) -> IO a
+ IO, -- instance MonadFix
+ fixIO, -- :: (a -> IO a) -> IO a
-- * Files and handles
- FilePath, -- :: String
+ FilePath, -- :: String
- Handle, -- abstract, instance of: Eq, Show.
+ Handle, -- abstract, instance of: Eq, Show.
-- ** Standard handles
-- ** Opening files
- openFile, -- :: FilePath -> IOMode -> IO Handle
+ withFile,
+ openFile, -- :: FilePath -> IOMode -> IO Handle
IOMode(ReadMode,WriteMode,AppendMode,ReadWriteMode),
-- ** Closing files
- hClose, -- :: Handle -> IO ()
+ hClose, -- :: Handle -> IO ()
-- ** Special cases
-- | These functions are also exported by the "Prelude".
- readFile, -- :: FilePath -> IO String
- writeFile, -- :: FilePath -> String -> IO ()
- appendFile, -- :: FilePath -> String -> IO ()
+ readFile, -- :: FilePath -> IO String
+ writeFile, -- :: FilePath -> String -> IO ()
+ appendFile, -- :: FilePath -> String -> IO ()
-- ** File locking
-- * Operations on handles
- -- ** Determining the size of a file
+ -- ** Determining and changing the size of a file
- hFileSize, -- :: Handle -> IO Integer
+ hFileSize, -- :: Handle -> IO Integer
+#ifdef __GLASGOW_HASKELL__
+ hSetFileSize, -- :: Handle -> Integer -> IO ()
+#endif
-- ** Detecting the end of input
- hIsEOF, -- :: Handle -> IO Bool
- isEOF, -- :: IO Bool
+ hIsEOF, -- :: Handle -> IO Bool
+ isEOF, -- :: IO Bool
-- ** Buffering operations
BufferMode(NoBuffering,LineBuffering,BlockBuffering),
- hSetBuffering, -- :: Handle -> BufferMode -> IO ()
- hGetBuffering, -- :: Handle -> IO BufferMode
- hFlush, -- :: Handle -> IO ()
+ hSetBuffering, -- :: Handle -> BufferMode -> IO ()
+ hGetBuffering, -- :: Handle -> IO BufferMode
+ hFlush, -- :: Handle -> IO ()
-- ** Repositioning handles
- hGetPosn, -- :: Handle -> IO HandlePosn
- hSetPosn, -- :: HandlePosn -> IO ()
+ hGetPosn, -- :: Handle -> IO HandlePosn
+ hSetPosn, -- :: HandlePosn -> IO ()
HandlePosn, -- abstract, instance of: Eq, Show.
- hSeek, -- :: Handle -> SeekMode -> Integer -> IO ()
+ hSeek, -- :: Handle -> SeekMode -> Integer -> IO ()
SeekMode(AbsoluteSeek,RelativeSeek,SeekFromEnd),
#if !defined(__NHC__)
- hTell, -- :: Handle -> IO Integer
+ hTell, -- :: Handle -> IO Integer
#endif
-- ** Handle properties
hIsReadable, hIsWritable, -- :: Handle -> IO Bool
hIsSeekable, -- :: Handle -> IO Bool
- -- ** Terminal operations
+ -- ** Terminal operations (not portable: GHC\/Hugs only)
#if !defined(__NHC__)
- hIsTerminalDevice, -- :: Handle -> IO Bool
+ hIsTerminalDevice, -- :: Handle -> IO Bool
- hSetEcho, -- :: Handle -> Bool -> IO ()
- hGetEcho, -- :: Handle -> IO Bool
+ hSetEcho, -- :: Handle -> Bool -> IO ()
+ hGetEcho, -- :: Handle -> IO Bool
#endif
- -- ** Showing handle state
+ -- ** Showing handle state (not portable: GHC only)
#ifdef __GLASGOW_HASKELL__
- hShow, -- :: Handle -> IO String
+ hShow, -- :: Handle -> IO String
#endif
-- * Text input and output
-- ** Text input
- hWaitForInput, -- :: Handle -> Int -> IO Bool
- hReady, -- :: Handle -> IO Bool
- hGetChar, -- :: Handle -> IO Char
- hGetLine, -- :: Handle -> IO [Char]
- hLookAhead, -- :: Handle -> IO Char
- hGetContents, -- :: Handle -> IO [Char]
+ hWaitForInput, -- :: Handle -> Int -> IO Bool
+ hReady, -- :: Handle -> IO Bool
+ hGetChar, -- :: Handle -> IO Char
+ hGetLine, -- :: Handle -> IO [Char]
+ hLookAhead, -- :: Handle -> IO Char
+ hGetContents, -- :: Handle -> IO [Char]
-- ** Text output
- hPutChar, -- :: Handle -> Char -> IO ()
- hPutStr, -- :: Handle -> [Char] -> IO ()
- hPutStrLn, -- :: Handle -> [Char] -> IO ()
- hPrint, -- :: Show a => Handle -> a -> IO ()
+ hPutChar, -- :: Handle -> Char -> IO ()
+ hPutStr, -- :: Handle -> [Char] -> IO ()
+ hPutStrLn, -- :: Handle -> [Char] -> IO ()
+ hPrint, -- :: Show a => Handle -> a -> IO ()
-- ** Special cases for standard input and output
-- | These functions are also exported by the "Prelude".
- interact, -- :: (String -> String) -> IO ()
- putChar, -- :: Char -> IO ()
- putStr, -- :: String -> IO ()
- putStrLn, -- :: String -> IO ()
- print, -- :: Show a => a -> IO ()
- getChar, -- :: IO Char
- getLine, -- :: IO String
- getContents, -- :: IO String
- readIO, -- :: Read a => String -> IO a
- readLn, -- :: Read a => IO a
+ interact, -- :: (String -> String) -> IO ()
+ putChar, -- :: Char -> IO ()
+ putStr, -- :: String -> IO ()
+ putStrLn, -- :: String -> IO ()
+ print, -- :: Show a => a -> IO ()
+ getChar, -- :: IO Char
+ getLine, -- :: IO String
+ getContents, -- :: IO String
+ readIO, -- :: Read a => String -> IO a
+ readLn, -- :: Read a => IO a
-- * Binary input and output
-#if !defined(__NHC__)
- openBinaryFile, -- :: FilePath -> IOMode -> IO Handle
- hSetBinaryMode, -- :: Handle -> Bool -> IO ()
- hPutBuf, -- :: Handle -> Ptr a -> Int -> IO ()
- hGetBuf, -- :: Handle -> Ptr a -> Int -> IO Int
-#endif
+ withBinaryFile,
+ openBinaryFile, -- :: FilePath -> IOMode -> IO Handle
+ hSetBinaryMode, -- :: Handle -> Bool -> IO ()
+ hPutBuf, -- :: Handle -> Ptr a -> Int -> IO ()
+ hGetBuf, -- :: Handle -> Ptr a -> Int -> IO Int
#if !defined(__NHC__) && !defined(__HUGS__)
- hPutBufNonBlocking, -- :: Handle -> Ptr a -> Int -> IO Int
- hGetBufNonBlocking, -- :: Handle -> Ptr a -> Int -> IO Int
+ hPutBufNonBlocking, -- :: Handle -> Ptr a -> Int -> IO Int
+ hGetBufNonBlocking, -- :: Handle -> Ptr a -> Int -> IO Int
#endif
- module System.IO.Error,
+ -- * Temporary files
+
+ openTempFile,
+ openBinaryTempFile,
) where
+import Control.Exception hiding (bracket)
+
+#ifndef __NHC__
+import Data.Bits
+import Data.List
+import Data.Maybe
+import Foreign.C.Error
+import Foreign.C.String
+import System.Posix.Internals
+#endif
+
+#ifdef __GLASGOW_HASKELL__
+import GHC.IOBase as ExceptionBase
+#endif
+#ifdef __HUGS__
+import Hugs.Exception as ExceptionBase
+#endif
+
#ifdef __GLASGOW_HASKELL__
import GHC.Base
-import GHC.IOBase -- Together these four Prelude modules define
-import GHC.Handle -- all the stuff exported by IO for the GHC version
+import GHC.IOBase -- Together these four Prelude modules define
+import GHC.Handle -- all the stuff exported by IO for the GHC version
import GHC.IO
-import GHC.ST ( fixST )
import GHC.Exception
import GHC.Num
import GHC.Read
import Hugs.IO
import Hugs.IOExts
import Hugs.IORef
-import Hugs.Prelude ( throw, Exception(NonTermination) )
-import System.IO.Unsafe ( unsafeInterleaveIO )
+import Hugs.Prelude ( throw, Exception(NonTermination) )
+import Control.Exception ( bracket )
+import System.IO.Unsafe ( unsafeInterleaveIO )
#endif
#ifdef __NHC__
, hGetContents -- :: Handle -> IO [Char]
, hPutChar -- :: Handle -> Char -> IO ()
, hPutStr -- :: Handle -> [Char] -> IO ()
+ , hPutStrLn -- :: Handle -> [Char] -> IO ()
+ , hPrint -- :: Handle -> [Char] -> IO ()
+ , hReady -- :: Handle -> [Char] -> IO ()
, hIsOpen, hIsClosed -- :: Handle -> IO Bool
, hIsReadable, hIsWritable -- :: Handle -> IO Bool
, hIsSeekable -- :: Handle -> IO Bool
+ , bracket
, IO ()
, FilePath -- :: String
)
-import NHC.IOExtras (fixIO)
+import NHC.IOExtras (fixIO, hPutBuf, hGetBuf)
+import NHC.FFI (Ptr)
#endif
-import System.IO.Error (
- isAlreadyExistsError, isDoesNotExistError, -- :: IOError -> Bool
- isAlreadyInUseError, isFullError,
- isEOFError, isIllegalOperation,
- isPermissionError, isUserError,
-
- ioeGetErrorString, -- :: IOError -> String
- ioeGetHandle, -- :: IOError -> Maybe Handle
- ioeGetFileName, -- :: IOError -> Maybe FilePath
-
- try, -- :: IO a -> IO (Either IOError a)
-
- -- re-exports of Prelude names
- IOError,
- ioError, -- :: IOError -> IO a
- userError, -- :: String -> IOError
- catch -- :: IO a -> (IOError -> IO a) -> IO a
- )
-
-- -----------------------------------------------------------------------------
-- Standard IO
-#ifndef __HUGS__
+#ifdef __GLASGOW_HASKELL__
-- | Write a character to the standard output device
-- (same as 'hPutChar' 'stdout').
-- The file is read lazily, on demand, as with 'getContents'.
readFile :: FilePath -> IO String
-readFile name = openFile name ReadMode >>= hGetContents
+readFile name = openFile name ReadMode >>= hGetContents
-- | The computation 'writeFile' @file str@ function writes the string @str@,
-- to the file @file@.
-
-writeFile :: FilePath -> String -> IO ()
-writeFile name str = do
- hdl <- openFile name WriteMode
- hPutStr hdl str
- hClose hdl
+writeFile :: FilePath -> String -> IO ()
+writeFile f txt = withFile f WriteMode (\ hdl -> hPutStr hdl txt)
-- | The computation 'appendFile' @file str@ function appends the string @str@,
-- to the file @file@.
-- > main = appendFile "squares" (show [(x,x*x) | x <- [0,0.1..2]])
appendFile :: FilePath -> String -> IO ()
-appendFile name str = do
- hdl <- openFile name AppendMode
- hPutStr hdl str
- hClose hdl
+appendFile f txt = withFile f AppendMode (\ hdl -> hPutStr hdl txt)
-- | The 'readLn' function combines 'getLine' and 'readIO'.
readIO :: Read a => String -> IO a
readIO s = case (do { (x,t) <- reads s ;
- ("","") <- lex t ;
+ ("","") <- lex t ;
return x }) of
- [x] -> return x
- [] -> ioError (userError "Prelude.readIO: no parse")
- _ -> ioError (userError "Prelude.readIO: ambiguous parse")
-#endif /* __HUGS__ */
+ [x] -> return x
+ [] -> ioError (userError "Prelude.readIO: no parse")
+ _ -> ioError (userError "Prelude.readIO: ambiguous parse")
+#endif /* __GLASGOW_HASKELL__ */
+#ifndef __NHC__
-- | Computation 'hReady' @hdl@ indicates whether at least one item is
-- available for input from handle @hdl@.
--
-- This operation may fail with:
--
--- * 'isEOFError' if the end of file has been reached.
+-- * 'System.IO.Error.isEOFError' if the end of file has been reached.
-hReady :: Handle -> IO Bool
-hReady h = hWaitForInput h 0
+hReady :: Handle -> IO Bool
+hReady h = hWaitForInput h 0
-- | The same as 'hPutStr', but adds a newline character.
-hPutStrLn :: Handle -> String -> IO ()
+hPutStrLn :: Handle -> String -> IO ()
hPutStrLn hndl str = do
hPutStr hndl str
hPutChar hndl '\n'
--
-- This operation may fail with:
--
--- * 'isFullError' if the device is full; or
+-- * 'System.IO.Error.isFullError' if the device is full; or
--
--- * 'isPermissionError' if another system resource limit would be exceeded.
-
-hPrint :: Show a => Handle -> a -> IO ()
-hPrint hdl = hPutStrLn hdl . show
+-- * 'System.IO.Error.isPermissionError' if another system resource limit would be exceeded.
+
+hPrint :: Show a => Handle -> a -> IO ()
+hPrint hdl = hPutStrLn hdl . show
+#endif /* !__NHC__ */
+
+-- | @'withFile' name mode act@ opens a file using 'openFile' and passes
+-- the resulting handle to the computation @act@. The handle will be
+-- closed on exit from 'withFile', whether by normal termination or by
+-- raising an exception.
+withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
+withFile name mode = bracket (openFile name mode) hClose
+
+-- | @'withBinaryFile' name mode act@ opens a file using 'openBinaryFile'
+-- and passes the resulting handle to the computation @act@. The handle
+-- will be closed on exit from 'withBinaryFile', whether by normal
+-- termination or by raising an exception.
+withBinaryFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
+withBinaryFile name mode = bracket (openBinaryFile name mode) hClose
-- ---------------------------------------------------------------------------
-- fixIO
-- computation a few times before it notices the loop, which is wrong.
#endif
+#if defined(__NHC__)
+-- Assume a unix platform, where text and binary I/O are identical.
+openBinaryFile = openFile
+hSetBinaryMode _ _ = return ()
+#endif
+
+-- | The function creates a temporary file in ReadWrite mode.
+-- The created file isn\'t deleted automatically, so you need to delete it manually.
+--
+-- The file is creates with permissions such that only the current
+-- user can read\/write it.
+--
+-- With some exceptions (see below), the file will be created securely
+-- in the sense that an attacker should not be able to cause
+-- openTempFile to overwrite another file on the filesystem using your
+-- credentials, by putting symbolic links (on Unix) in the place where
+-- the temporary file is to be created. On Unix the @O_CREAT@ and
+-- @O_EXCL@ flags are used to prevent this attack, but note that
+-- @O_EXCL@ is sometimes not supported on NFS filesystems, so if you
+-- rely on this behaviour it is best to use local filesystems only.
+--
+openTempFile :: FilePath -- ^ Directory in which to create the file
+ -> String -- ^ File name template. If the template is \"foo.ext\" then
+ -- the created file will be \"fooXXX.ext\" where XXX is some
+ -- random number.
+ -> IO (FilePath, Handle)
+openTempFile tmp_dir template = openTempFile' "openTempFile" tmp_dir template False
+
+-- | Like 'openTempFile', but opens the file in binary mode. See 'openBinaryFile' for more comments.
+openBinaryTempFile :: FilePath -> String -> IO (FilePath, Handle)
+openBinaryTempFile tmp_dir template = openTempFile' "openBinaryTempFile" tmp_dir template True
+
+openTempFile' :: String -> FilePath -> String -> Bool -> IO (FilePath, Handle)
+openTempFile' loc tmp_dir template binary = do
+ pid <- c_getpid
+ findTempName pid
+ where
+ -- We split off the last extension, so we can use .foo.ext files
+ -- for temporary files (hidden on Unix OSes). Unfortunately we're
+ -- below filepath in the hierarchy here.
+ (prefix,suffix) =
+ case break (== '.') $ reverse template of
+ -- First case: template contains no '.'s. Just re-reverse it.
+ (rev_suffix, "") -> (reverse rev_suffix, "")
+ -- Second case: template contains at least one '.'. Strip the
+ -- dot from the prefix and prepend it to the suffix (if we don't
+ -- do this, the unique number will get added after the '.' and
+ -- thus be part of the extension, which is wrong.)
+ (rev_suffix, '.':rest) -> (reverse rest, '.':reverse rev_suffix)
+ -- Otherwise, something is wrong, because (break (== '.')) should
+ -- always return a pair with either the empty string or a string
+ -- beginning with '.' as the second component.
+ _ -> error "bug in System.IO.openTempFile"
+
+#ifndef __NHC__
+ oflags1 = rw_flags .|. o_EXCL
+
+ binary_flags
+ | binary = o_BINARY
+ | otherwise = 0
+
+ oflags = oflags1 .|. binary_flags
+#endif
+
+#ifdef __NHC__
+ findTempName x = do h <- openFile filepath ReadWriteMode
+ return (filepath, h)
+#else
+ findTempName x = do
+ fd <- withCString filepath $ \ f ->
+ c_open f oflags 0o600
+ if fd < 0
+ then do
+ errno <- getErrno
+ if errno == eEXIST
+ then findTempName (x+1)
+ else ioError (errnoToIOError loc errno Nothing (Just tmp_dir))
+ else do
+ -- XXX We want to tell fdToHandle what the filepath is,
+ -- as any exceptions etc will only be able to report the
+ -- fd currently
+ h <- fdToHandle fd
+ `ExceptionBase.catchAny` \e -> do c_close fd; throw e
+ return (filepath, h)
+#endif
+ where
+ filename = prefix ++ show x ++ suffix
+ filepath = tmp_dir `combine` filename
+
+ -- XXX bits copied from System.FilePath, since that's not available here
+ combine a b
+ | null b = a
+ | null a = b
+ | last a == pathSeparator = a ++ b
+ | otherwise = a ++ [pathSeparator] ++ b
+
+#if __HUGS__
+ fdToHandle fd = openFd (fromIntegral fd) False ReadWriteMode binary
+#endif
+
+-- XXX Should use filepath library
+pathSeparator :: Char
+#ifdef mingw32_HOST_OS
+pathSeparator = '\\'
+#else
+pathSeparator = '/'
+#endif
+
+#ifndef __NHC__
+-- XXX Copied from GHC.Handle
+std_flags = o_NONBLOCK .|. o_NOCTTY
+output_flags = std_flags .|. o_CREAT
+read_flags = std_flags .|. o_RDONLY
+write_flags = output_flags .|. o_WRONLY
+rw_flags = output_flags .|. o_RDWR
+append_flags = write_flags .|. o_APPEND
+#endif
+
+#ifdef __NHC__
+foreign import ccall "getpid" c_getpid :: IO Int
+#endif
+
-- $locking
-- Implementations should enforce as far as possible, at least locally to the
-- Haskell process, multiple-reader single-writer locking on files.
-- the file until the entire contents of the file have been consumed.
-- It follows that an attempt to write to a file (using 'writeFile', for
-- example) that was earlier opened by 'readFile' will usually result in
--- failure with 'isAlreadyInUseError'.
+-- failure with 'System.IO.Error.isAlreadyInUseError'.
+
+-- -----------------------------------------------------------------------------
+-- Utils
+
+#ifdef __GLASGOW_HASKELL__
+-- Copied here to avoid recursive dependency with Control.Exception
+bracket
+ :: IO a -- ^ computation to run first (\"acquire resource\")
+ -> (a -> IO b) -- ^ computation to run last (\"release resource\")
+ -> (a -> IO c) -- ^ computation to run in-between
+ -> IO c -- returns the value from the in-between computation
+bracket before after thing =
+ block (do
+ a <- before
+ r <- catchAny
+ (unblock (thing a))
+ (\e -> do { after a; throw e })
+ after a
+ return r
+ )
+#endif