winio-0.1: System/Windows/IO.hs
{-# LANGUAGE ForeignFunctionInterface #-}
{- |
Module : System.Windows.IO
Copyright : 2007-2009 Felix Martini
License : BSD-style (see LICENSE for license terms)
Maintainer : felix@felixmartini.com
Stability : Experimental
Low-level I/O functions using the Windows API.
-}
module System.Windows.IO
( HANDLE
, ShareMode(..)
, openFile
, openTempFile
, getFileSize
, setFileSize
, getPosition
, setPosition
, associateWithPort
, associateSocketWithPort
, read
, write
, send
, sendTo
, recv
, recvFrom
, readBS
, writeBS
, sendBS
, sendToBS
, recvBS
, recvFromBS
, closeHandle
) where
import Prelude hiding (read)
import Control.Concurrent (ThreadId)
import Control.Concurrent.MVar (MVar)
import Control.Monad (when)
import Data.Bits ((.|.))
import Data.ByteString.Internal (ByteString(..))
import Data.Int (Int64)
import Data.IORef (IORef)
import Data.Word (Word8, Word32, Word64)
import Foreign.C.String (CWString)
import Foreign.C.Types (CInt)
import Foreign.Ptr (Ptr)
import Foreign.StablePtr (StablePtr)
import Network.Socket (Socket(..), SockAddr)
import System.IO (IOMode(..), SeekMode(..), FilePath)
import System.IO.Unsafe (unsafePerformIO)
import System.Windows.Error
import qualified Control.Concurrent as Concur
import qualified Control.Concurrent.MVar as MVar
import qualified Control.Exception.Extensible as Ex
import qualified Data.ByteString.Internal as B
import qualified Data.IORef as IORef
import qualified Network.Socket as Socket
import qualified Network.Socket.Internal as Socket
import qualified Foreign.C.String as CString
import qualified Foreign.Ptr as Ptr
import qualified Foreign.ForeignPtr as ForeignPtr
import qualified Foreign.Marshal as Marshal
import qualified Foreign.StablePtr as StablePtr
import qualified Foreign.Storable as Storable
import qualified System.IO.Error as IOErr
-- -------------
-- General types
-- | A Windows handle.
type HANDLE = Ptr ()
-- | File share mode. Determines if other processes can access the
-- open file.
data ShareMode = ShareRead
| ShareWrite
| ShareReadWrite
| NotShared
deriving (Show)
-- ---------------------
-- Windows API constants
-- File pointer
fileBegin = 0 :: Word32
fileCurrent = 1 :: Word32
fileEnd = 2 :: Word32
-- Access rights
genericRead = 0x80000000 :: Word32
genericWrite = 0x40000000 :: Word32
genericExecute = 0x20000000 :: Word32
genericAll = 0x10000000 :: Word32
fileAppendData = 0x00000004 :: Word32
-- Share modes
fileShareRead = 0x00000001 :: Word32
fileShareWrite = 0x00000002 :: Word32
fileShareDelete = 0x00000004 :: Word32
-- Attributes and flags
fileAttributeReadonly = 0x00000001 :: Word32
fileAttributeNormal = 0x00000080 :: Word32
fileAttributeTemporary = 0x00000100 :: Word32
fileFlagOverlapped = 0x40000000 :: Word32
-- Creation disposition
createNew = 1 :: Word32
createAlways = 2 :: Word32
openExisting = 3 :: Word32
openAlways = 4 :: Word32
truncateExisting = 5 :: Word32
-- Infinite timeout
infinite :: Word32
infinite = 0xFFFFFFFF
-- --------------
-- File functions
-- | Open a file. The share mode determines if other processes can
-- access the open file.
openFile :: FilePath -> IOMode -> ShareMode -> IO HANDLE
openFile filepath iomode sharemode = do
let (access, create) =
case iomode of
ReadMode -> (genericRead, openExisting)
WriteMode -> (genericWrite, createAlways)
ReadWriteMode -> (genericRead .|. genericWrite, openAlways)
AppendMode -> (fileAppendData, openAlways)
let share =
case sharemode of
ShareRead -> fileShareRead
ShareWrite -> fileShareWrite
ShareReadWrite -> fileShareRead .|. fileShareWrite
NotShared -> 0
hdl <- CString.withCWString filepath $ \fp ->
throwWinErrorIfInvalidHandle $
c_CreateFile fp
access
share
Ptr.nullPtr
create
fileFlagOverlapped
Ptr.nullPtr
associateWithPort hdl
return hdl
-- | Get the file size.
getFileSize :: HANDLE -> IO Int64
getFileSize hdl = Marshal.alloca $ \s -> do
throwWinErrorIfFalse $ c_GetFileSize hdl s
Storable.peek s
-- | Set a new file size.
setFileSize :: HANDLE -> Int64 -> IO ()
setFileSize hdl s = do
fp <- getPosition hdl
setPosition hdl s AbsoluteSeek
throwWinErrorIfFalse $ c_SetEndOfFile hdl
setPosition hdl fp AbsoluteSeek
-- | Get the current file position.
getPosition :: HANDLE -> IO Int64
getPosition hdl = Marshal.alloca $ \pos -> do
throwWinErrorIfFalse $ c_GetFilePointer hdl pos
Storable.peek pos
-- | Set a new file position.
setPosition :: HANDLE -> Int64 -> SeekMode -> IO ()
setPosition hdl offset mode = do
let method = case mode of
AbsoluteSeek -> fileBegin
RelativeSeek -> fileCurrent
SeekFromEnd -> fileEnd
throwWinErrorIfFalse $ c_SetFilePointer hdl offset method
-- ---------------
-- Temporary Files
-- Maximum allowed path length.
maxPath :: Int
maxPath = 260
-- | Get the default temporary directory.
getTempPath :: IO FilePath
getTempPath =
-- 16-bit chars
Marshal.allocaBytes (fromIntegral (2 * maxPath)) $ \tmp_path -> do
throwWinErrorIf (== 0) $
c_GetTempPath (fromIntegral maxPath) tmp_path
CString.peekCWString tmp_path
-- | Get a unique temporary file name. No more than three characters of
-- the prefix string are used. An empty prefix string is allowed.
getTempFileName :: FilePath -> String -> IO FilePath
getTempFileName dir prefix = do
when (length dir > maxPath - 14) $
ioError $ IOErr.mkIOError
IOErr.illegalOperationErrorType
"System.Windows.IO.getTempFileName: \
\Path length is too long."
Nothing (Just dir)
dir' <- if null dir then getTempPath else return dir
let prefix3 = take 3 prefix
Marshal.allocaBytes (2 * maxPath) $ \filename ->
CString.withCWString dir' $ \d ->
CString.withCWString prefix3 $ \p3 -> do
throwWinErrorIf (== 0) $
c_GetTempFileName d p3 0 filename
CString.peekCWString filename
-- | Open a temporary file with a filename prefix. If the directory
-- path is an empty string then the default temporary directory will be
-- used. No more than three characters of the prefix string are used.
-- An empty prefix string is allowed.
openTempFile :: FilePath -> String -> IO (FilePath, HANDLE)
openTempFile dir prefix = do
filename <- getTempFileName dir prefix
hdl <- CString.withCWString filename $ \file -> do
throwWinErrorIfInvalidHandle $
c_CreateFile file
(genericRead .|. genericWrite)
0
Ptr.nullPtr
createAlways
(fileFlagOverlapped .|. fileAttributeTemporary)
Ptr.nullPtr
return (filename, hdl)
-- -------------------------
-- I/O manager (threaded RTS)
-- | I/O completion port.
completionPort :: IORef HANDLE
{-# NOINLINE completionPort #-}
completionPort = unsafePerformIO $ do
hdl <- throwWinErrorIfInvalidHandle (c_NewCompletionPort 1)
startIOManager hdl
IORef.newIORef hdl
-- | Associate a Windows handle with the I/O completion port.
associateWithPort :: HANDLE -> IO ()
associateWithPort hdl = do
port <- IORef.readIORef completionPort
throwWinErrorIfFalse (c_AssociateHandleWithPort hdl port)
-- | Associate a socket with the I/O completion port.
associateSocketWithPort :: Socket -> IO ()
associateSocketWithPort sock = do
port <- IORef.readIORef completionPort
throwWinErrorIfFalse $
c_AssociateSocketWithPort (Socket.fdSocket sock) port
-- | Start the IO manager.
startIOManager :: HANDLE -> IO ThreadId
startIOManager port = Concur.forkIO (handleCompletions port)
-- | Handle completed I/O actions obtained from an I/O completion port.
handleCompletions :: HANDLE -> IO ()
handleCompletions port = do
Marshal.alloca $ \num_bytes_ptr -> Marshal.alloca $ \userdata_ptr -> do
res <- c_GetCompletion port num_bytes_ptr userdata_ptr infinite
err <- if not res then getWinError else return errorSuccess
if userdata_ptr == Ptr.nullPtr
then Ex.throwIO err
else do
mvar_stable_ptr <- Storable.peek userdata_ptr
mvar <- StablePtr.deRefStablePtr mvar_stable_ptr
if err /= errorSuccess
then do
success <- MVar.tryPutMVar mvar (Left err)
when (not success) $ fail
"System.Windows.IO.handleCompletions: \
\MVar is not empty."
else do
num_bytes <-
fmap fromIntegral $! Storable.peek num_bytes_ptr :: IO Int
success <- MVar.tryPutMVar mvar (Right num_bytes)
when (not success) $ fail
"System.Windows.IO.handleCompletions: \
\MVar is not empty."
handleCompletions port
-- ---------------
-- Asynchronous I/O
errorIOPending :: WinError
errorIOPending = WinError 997
waitForCompletion :: (StablePtr (MVar (Either WinError Int)) -> IO CInt)
-> IO Int
waitForCompletion async_op = do
wait_var <- MVar.newEmptyMVar
Ex.bracket (StablePtr.newStablePtr wait_var) StablePtr.freeStablePtr $
\wait_var_ptr -> do
res <- async_op wait_var_ptr
if res == (-1)
then do
err <- getWinError
if err == errorIOPending
then do
-- The operation is initiated asynchronously.
-- We wait for the completion of the operation.
res' <- MVar.takeMVar wait_var
case res' of
Right num_bytes -> return num_bytes
Left e | e == errorHandleEOF -> return 0
| otherwise -> Ex.throwIO e
else
-- An error occurred.
if err == errorHandleEOF then return 0 else Ex.throwIO err
else
-- The operation is completed synchronously.
return $ fromIntegral res
-- | Read bytes from a handle into a buffer. The actual number of bytes
-- read depends on the handle and may be less than the number of bytes
-- requested. The function returns zero if the end of input has been
-- reached.
read :: HANDLE -> Int64 -> Ptr Word8 -> Int -> IO Int
read hdl pos buf buf_size =
waitForCompletion $
c_AsyncRead hdl (fromIntegral pos) buf (fromIntegral buf_size)
-- | Write bytes from a buffer to a handle.
write :: HANDLE -> Int64 -> Ptr Word8 -> Int -> IO Int
write hdl pos buf buf_size =
waitForCompletion $
c_AsyncWrite hdl (fromIntegral pos) buf (fromIntegral buf_size)
-- | Send bytes from a buffer.
send :: Socket -> Ptr Word8 -> Int -> IO Int
send (MkSocket sd _ _ _ _) buf buf_size =
waitForCompletion $
c_AsyncSend sd buf (fromIntegral buf_size)
-- | Send bytes from a buffer to a remote address.
sendTo :: Socket -> Ptr Word8 -> Int -> SockAddr -> IO Int
sendTo (MkSocket sd _ _ _ _) buf buf_size addr =
let addr_size = Socket.sizeOfSockAddr addr in
Marshal.allocaBytes addr_size $ \addr_ptr -> do
Socket.pokeSockAddr addr_ptr addr
waitForCompletion $
c_AsyncSendTo sd buf (fromIntegral buf_size)
addr_ptr (fromIntegral addr_size)
-- | Receive bytes from a socket into a buffer. The actual number of
-- bytes received may be less than the number of bytes requested. The
-- function returns zero if the end of input has been reached.
recv :: Socket -> Ptr Word8 -> Int -> IO Int
recv (MkSocket sd _ _ _ _) buf buf_size =
waitForCompletion $
c_AsyncRecv sd buf (fromIntegral buf_size)
-- | Receive bytes from a socket into a buffer. The actual number of
-- bytes received may be less than the number of bytes requested. The
-- function returns zero if the end of input has been reached.
recvFrom :: Socket -> Ptr a -> Int -> IO (Int, SockAddr)
recvFrom sock@(MkSocket sd family _ _ _) buf buf_size =
let addr_size = Socket.sizeOfSockAddrByFamily family in
Marshal.allocaBytes addr_size $ \addr_ptr ->
Marshal.alloca $ \addr_size_ptr -> do
Storable.poke addr_size_ptr (fromIntegral addr_size)
num <- waitForCompletion $
c_AsyncRecvFrom sd buf (fromIntegral buf_size) addr_ptr
addr_size_ptr
-- N.B. WSARecvFrom does not provide the source address for
-- connection-oriented sockets.
connected <- Socket.sIsConnected sock
maybe_addr <- if connected
then Socket.getPeerName sock
else Socket.peekSockAddr addr_ptr
return (num, maybe_addr)
-- --------------------------
-- strict ByteString versions
-- | Read bytes from a handle into a strict ByteString. The actual
-- number of bytes read depends on the handle and may be less
-- than the number of bytes requested. The function returns an empty
-- ByteString if the end of input has been reached.
readBS :: HANDLE -> Int64 -> Int -> IO ByteString
readBS hdl pos num_bytes =
B.createAndTrim num_bytes
(\buf -> read hdl pos buf num_bytes)
-- | Write bytes from a strict ByteString to a handle.
writeBS :: HANDLE -> Int64 -> ByteString -> IO Int
writeBS hdl pos (PS fp off len) =
ForeignPtr.withForeignPtr fp $ \p ->
write hdl pos (p `Ptr.plusPtr` off) len
-- | Send bytes from a strict ByteString.
sendBS :: Socket -> ByteString -> IO Int
sendBS sock (PS fp off len) =
ForeignPtr.withForeignPtr fp $ \p ->
send sock (p `Ptr.plusPtr` off) len
-- | Send bytes from a strict ByteString to a remote address.
sendToBS :: Socket -> ByteString -> SockAddr -> IO Int
sendToBS sock (PS fp off len) addr =
ForeignPtr.withForeignPtr fp $ \p ->
sendTo sock (p `Ptr.plusPtr` off) len addr
-- | Receive bytes into a strict ByteString. The actual number of bytes
-- received may be less than the number of bytes requested. The
-- function returns an empty ByteString if the end of input has been
-- reached.
recvBS :: Socket -> Int -> IO ByteString
recvBS sock num_bytes =
B.createAndTrim num_bytes (\buf -> recv sock buf num_bytes)
-- | Receive bytes into a strict ByteString. The actual number of bytes
-- received may be less than the number of bytes requested. The
-- function returns an empty ByteString if the end of input has been
-- reached.
recvFromBS :: Socket -> Int -> IO (ByteString, SockAddr)
recvFromBS sock@(MkSocket sd family _ _ _) num_bytes =
B.createAndTrim' num_bytes $ \buf -> do
(num_recvd, addr) <- recvFrom sock buf num_bytes
return (0, num_recvd, addr)
-- -----------------------
-- Miscellaneous functions
-- | Close a Windows handle.
closeHandle :: HANDLE -> IO ()
closeHandle hdl = throwWinErrorIfFalse $ c_CloseHandle hdl
-- -----------------
-- Foreign functions
foreign import stdcall unsafe "CloseHandle"
c_CloseHandle :: HANDLE -> IO Bool
foreign import ccall unsafe "HsNewCompletionPort"
c_NewCompletionPort :: Word32 -> IO HANDLE
foreign import ccall unsafe "HsAssociateHandleWithPort"
c_AssociateHandleWithPort :: HANDLE -> HANDLE -> IO Bool
foreign import ccall unsafe "HsAssociateSocketWithPort"
c_AssociateSocketWithPort :: CInt -> HANDLE -> IO Bool
foreign import ccall safe "HsGetCompletion"
c_GetCompletion :: HANDLE -> Ptr Word32 -> Ptr (StablePtr a)
-> Word32 -> IO Bool
foreign import ccall unsafe "HsAsyncRead"
c_AsyncRead :: HANDLE -> Word64 -> Ptr a -> CInt -> StablePtr b
-> IO CInt
foreign import ccall unsafe "HsAsyncWrite"
c_AsyncWrite :: HANDLE -> Word64 -> Ptr a -> CInt -> StablePtr b
-> IO CInt
foreign import ccall unsafe "HsAsyncRecv"
c_AsyncRecv :: CInt -> Ptr a -> CInt -> StablePtr b -> IO CInt
foreign import ccall unsafe "HsAsyncRecvFrom"
c_AsyncRecvFrom :: CInt -> Ptr a -> CInt -> Ptr SockAddr
-> Ptr CInt -> StablePtr b -> IO CInt
foreign import ccall unsafe "HsAsyncSend"
c_AsyncSend :: CInt -> Ptr a -> CInt -> StablePtr b -> IO CInt
foreign import ccall unsafe "HsAsyncSendTo"
c_AsyncSendTo :: CInt -> Ptr a -> CInt -> Ptr SockAddr
-> CInt -> StablePtr b -> IO CInt
foreign import stdcall unsafe "CreateFileW"
c_CreateFile :: CWString -> Word32 -> Word32 -> Ptr ()
-> Word32 -> Word32 -> HANDLE -> IO HANDLE
foreign import ccall unsafe "HsGetFileSize"
c_GetFileSize :: HANDLE -> Ptr Int64 -> IO Bool
foreign import stdcall unsafe "SetEndOfFile"
c_SetEndOfFile :: HANDLE -> IO Bool
foreign import ccall unsafe "HsGetFilePointer"
c_GetFilePointer :: HANDLE -> Ptr Int64 -> IO Bool
foreign import ccall unsafe "HsSetFilePointer"
c_SetFilePointer :: HANDLE -> Int64 -> Word32 -> IO Bool
foreign import stdcall unsafe "GetTempPathW"
c_GetTempPath :: Word32 -> CWString -> IO Word32
foreign import stdcall unsafe "GetTempFileNameW"
c_GetTempFileName :: CWString -> CWString -> Word32 -> CWString
-> IO Word32