packages feed

sendfile 0.6.2 → 0.7.11.6

raw patch · 11 files changed

Files

sendfile.cabal view
@@ -1,5 +1,5 @@ name:          sendfile-version:       0.6.2+version:       0.7.11.6 stability:     provisional synopsis:      A portable sendfile library description:   A library which exposes zero-copy sendfile functionality in a portable way. If a platform does not support sendfile, a fallback implementation in haskell is provided.@@ -10,25 +10,44 @@ license-file:  LICENSE author:        Matthew Elder <matt@mattelder.org> maintainer:    Jeremy Shaw <jeremy@n-heptane.com>-homepage:      http://patch-tag.com/r/mae/sendfile+homepage:      https://github.com/Happstack/sendfile category:      Network build-type:    Simple-cabal-version: >= 1.6+cabal-version: >= 1.10 +tested-with:+  GHC == 9.8.1+  GHC == 9.6.3+  GHC == 9.4.7+  GHC == 9.2.8+  GHC == 9.0.2+  GHC == 8.10.7+  GHC == 8.8.4+  GHC == 8.6.5+  GHC == 8.4.4+  GHC == 8.2.2+  GHC == 8.0.2+ flag portable     description: Explicitly enable portable sendfile support (implemented in Haskell)     default:     False  library+    default-language: Haskell2010     hs-source-dirs:  src      exposed-modules: Network.Socket.SendFile+                     Network.Socket.SendFile.Iter+                     Network.Socket.SendFile.Handle+                     Network.Socket.SendFile.Portable      other-modules:   Network.Socket.SendFile.Internal-    -    build-depends:   base >= 3 && < 5,-                     network >= 2 && < 3+                     Network.Socket.SendFile.Util +    build-depends:   base       >= 3       && < 5,+                     bytestring >= 0.9.1.4 && < 0.13,+                     network    >= 2       && < 3.3+     ghc-options: -Wall      if os(windows) && !flag(portable)@@ -38,7 +57,7 @@       other-modules: Network.Socket.SendFile.Win32     else       if os(linux) && !flag(portable)-        cpp-options: -DLINUX_SENDFILE+        cpp-options: -DLINUX_SENDFILE -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE.         other-modules: Network.Socket.SendFile.Linux       else         if os(freebsd) && !flag(portable)@@ -50,9 +69,7 @@             other-modules: Network.Socket.SendFile.Darwin           else             cpp-options: -DPORTABLE_SENDFILE-            build-depends: bytestring >= 0.9.1.4 && < 0.10,-                           network-bytestring >= 0.1.1.3 && < 0.2  source-repository head-    type:     darcs-    location: http://patch-tag.com/r/mae/sendfile/pullrepo+    type:     git+    location: https://github.com/Happstack/sendfile.git
src/Network/Socket/SendFile.hs view
@@ -8,15 +8,25 @@ module Network.Socket.SendFile (     ByteCount,     Offset,+    Iter(..), runIter,     -- * Safe functions (recommended)     sendFile,+    sendFileIterWith,     sendFile',+    sendFileIterWith',+    -- * Unsafe functions+    -- | These functions are unsafe simply because there is no guarantee that the 'Handle' used for output is actually bound to a 'Socket'. If it is not, it will result in a runtime error.     unsafeSendFile,+    unsafeSendFileIterWith,     unsafeSendFile',+    unsafeSendFileIterWith',     -- * Utility functions     sendFileMode     ) where-import qualified Network.Socket.SendFile.Internal (sendFile, sendFile', unsafeSendFile, unsafeSendFile', sendFileMode)+import qualified Network.Socket.SendFile.Internal (sendFile, sendFileIterWith, sendFile', sendFileIterWith', +                                                   unsafeSendFile, unsafeSendFileIterWith, unsafeSendFile', unsafeSendFileIterWith', +                                                   sendFileMode)+import Network.Socket.SendFile.Iter (Iter(..), runIter) import Network.Socket (Socket) import System.IO (Handle) @@ -33,15 +43,39 @@     -> IO () sendFile = Network.Socket.SendFile.Internal.sendFile +-- | The simplest interface. Simply give it an output `Socket` and the `FilePath` to the input file.+--+-- This variant takes a function to drive the iteration loop. See 'Iter' for more information.+sendFileIterWith+    :: (IO Iter -> IO a)+    -> Socket    -- ^ The output socket+    -> FilePath  -- ^ The path where the input file resides+    -> ByteCount -- ^ Maximum bytes to send per block (may send less)+    -> IO a+sendFileIterWith = Network.Socket.SendFile.Internal.sendFileIterWith+ -- | A more powerful interface than sendFile which accepts a starting offset, and the bytecount to send; the offset and the count must be a positive integer. The initial position of the input file handle matters not since the offset is absolute, and the final position may be different depending on the platform -- no assumptions can be made. sendFile'     :: Socket    -- ^ The output socket-    -> FilePath    -- ^ The input file handle+    -> FilePath    -- ^ The input file path     -> Offset    -- ^ The offset to start at     -> ByteCount -- ^ The number of bytes to send     -> IO () sendFile' = Network.Socket.SendFile.Internal.sendFile' +-- | A more powerful interface than sendFile which accepts a starting offset, and the bytecount to send; the offset and the count must be a positive integer. The initial position of the input file handle matters not since the offset is absolute, and the final position may be different depending on the platform -- no assumptions can be made.+--+-- This variant takes a function to drive the iteration loop. See 'Iter' for more information.+sendFileIterWith'+    :: (IO Iter -> IO a)+    -> Socket    -- ^ The output socket+    -> FilePath    -- ^ The input file path+    -> ByteCount -- ^ Maximum bytes to send per block (may send less)+    -> Offset    -- ^ The offset to start at+    -> ByteCount -- ^ The number of bytes to send+    -> IO a+sendFileIterWith' = Network.Socket.SendFile.Internal.sendFileIterWith'+ -- | The unsafe version of sendFile which accepts a `Handle` instead of a `Socket` for the output.  It will flush the output handle before sending any file data. unsafeSendFile     :: Handle   -- ^ The output handle@@ -49,6 +83,17 @@     -> IO () unsafeSendFile = Network.Socket.SendFile.Internal.unsafeSendFile +-- | The unsafe version of sendFile which accepts a `Handle` instead of a `Socket` for the output.  It will flush the output handle before sending any file data.+--+-- This variant takes a function to drive the iteration loop. See 'Iter' for more information.+unsafeSendFileIterWith+    :: (IO Iter -> IO a)+    -> Handle    -- ^ The output handle+    -> FilePath  -- ^ The path where the input file resides+    -> ByteCount -- ^ Maximum bytes to send per block (may send less)+    -> IO a+unsafeSendFileIterWith = Network.Socket.SendFile.Internal.unsafeSendFileIterWith+ -- | The unsafe version of sendFile' which accepts a `Handle` instead of a `Socket` for the output. It will flush the output handle before sending any file data. unsafeSendFile'     :: Handle    -- ^ The output handle@@ -58,9 +103,22 @@     -> IO () unsafeSendFile' = Network.Socket.SendFile.Internal.unsafeSendFile' +-- | The unsafe version of sendFile' which accepts a `Handle` instead of a `Socket` for the output. It will flush the output handle before sending any file data.+--+-- This variant takes a function to drive the iteration loop. See 'Iter' for more information.+unsafeSendFileIterWith'+    :: (IO Iter -> IO a)+    -> Handle    -- ^ The output handle+    -> FilePath  -- ^ The input filepath+    -> ByteCount -- ^ The number of bytes to send+    -> Offset    -- ^ The offset to start at+    -> ByteCount -- ^ The number of bytes to send+    -> IO a+unsafeSendFileIterWith' = Network.Socket.SendFile.Internal.unsafeSendFileIterWith'+ -- | Returns the mode that sendfile was compiled with. Mainly for debugging use.--- | Possible values are 'WIN32_SENDFILE', 'LINUX_SENDFILE', 'FREEBSD_SENDFILE',--- | 'DARWIN_SENDFILE', and 'PORTABLE_SENDFILE'.+-- Possible values are 'WIN32_SENDFILE', 'LINUX_SENDFILE', 'FREEBSD_SENDFILE',+-- 'DARWIN_SENDFILE', and 'PORTABLE_SENDFILE'. sendFileMode :: String -- ^ The mode that sendfile was compiled with sendFileMode = Network.Socket.SendFile.Internal.sendFileMode 
src/Network/Socket/SendFile/Darwin.hsc view
@@ -1,58 +1,78 @@ {-# LANGUAGE ForeignFunctionInterface #-} -- | Darwin system-dependent code for 'sendfile'.-module Network.Socket.SendFile.Darwin (_sendFile) where+module Network.Socket.SendFile.Darwin (_sendFile, sendFileIter, sendfile) where+ import Data.Int (Int64)-import Foreign.C.Error (eAGAIN, getErrno, throwErrno)-import Foreign.C.Types (CInt)+import Foreign.C.Error (eAGAIN, eINTR, getErrno, throwErrno)+import Foreign.C.Types (CInt(..)) import Foreign.Marshal (alloca) import Foreign.Ptr (Ptr, nullPtr) import Foreign.Storable (peek, poke)-import System.Posix.Types (Fd)-import Control.Concurrent (threadWaitWrite)--{--  This is based on Linux.hsc (from version 0.5), although the-  calling convention for Darwin's sendfile() is closer to that-  of FreeBSD.--}+import Network.Socket.SendFile.Iter (Iter(..), runIter)+import System.Posix.Types (Fd(..))+#include <stdio.h>  _sendFile :: Fd -> Fd -> Int64 -> Int64 -> IO ()-_sendFile out_fd in_fd off count = do-  alloca $ \pbytes -> do-    rsendfile out_fd in_fd pbytes off count+_sendFile out_fd in_fd off count =+    do _ <- runIter (sendFileIter out_fd in_fd count off count) -- set blockSize == count. ie. send it all if we can.+       return () -rsendfile :: Fd -> Fd -> Ptr Int64 -> Int64 -> Int64 -> IO ()-rsendfile _      _     _      _    0        = return ()-rsendfile out_fd in_fd pbytes off remaining = do-  let bytes = min remaining maxBytes-  sbytes <- sendfile out_fd in_fd pbytes off bytes 0-  rsendfile out_fd in_fd pbytes off (remaining - sbytes)+-- | a way to send things in chunks+sendFileIter :: Fd -- ^ file descriptor corresponding to network socket+             -> Fd -- ^ file descriptor corresponding to file+             -> Int64 -- ^ maximum number of bytes to send at once+             -> Int64 -- ^ offset into file+             -> Int64 -- ^ total number of bytes to send+             -> IO Iter+sendFileIter out_fd in_fd blockSize off remaining =+        sendFileIterI out_fd in_fd (min blockSize maxBytes) off remaining -{-- Unlike the Linux implementation we track any re-sends due- to getting eAGAIN. If this wasn't done then the call would- appear to hang. The alternative would be to just -    return bytes- but I am unclear whether there are occasions this may- be wrong.--}+sendFileIterI :: Fd -- ^ file descriptor corresponding to network socket+              -> Fd -- ^ file descriptor corresponding to file+              -> Int64 -- ^ maximum number of bytes to send at once+              -> Int64 -- ^ offset into file+              -> Int64 -- ^ total number of bytes to send+--              -> Ptr Int64 -- ^ sent bytes ptr+              -> IO Iter+sendFileIterI _out_fd _in_fd _blockSize _off  0         = return (Done 0)+sendFileIterI  out_fd  in_fd  blockSize  off  remaining =+    do let bytes = min remaining blockSize+       (wouldBlock, nsent) <- alloca $ \len ->+                                do poke len bytes+                                   sendfileI out_fd in_fd off len+       let cont = sendFileIterI out_fd in_fd blockSize (off + nsent) (remaining `safeMinus` nsent)+       case wouldBlock of+         True  -> return (WouldBlock (fromIntegral nsent) out_fd cont)+         False -> return (Sent (fromIntegral nsent) cont) -sendfile :: Fd -> Fd -> Ptr Int64 -> Int64 -> Int64 -> Int64 -> IO Int64-sendfile out_fd in_fd pbytes off bytes ctr = do-  poke pbytes bytes-  -- Copying Linux implementation for thread synchronization,-  -- is this sensible?-  threadWaitWrite out_fd-  status <- c_sendfile out_fd in_fd off pbytes-  nsent <- peek pbytes-  if status == 0-    then return $ (fromIntegral nsent) + ctr-    else do errno <- getErrno-            if errno == eAGAIN-              -- is it correct to reduce bytes by nsent here?-              then let nremaining = max 0 (bytes - (fromIntegral nsent))-                   in sendfile out_fd in_fd pbytes (off + nsent) nremaining (ctr + (fromIntegral nsent))-              else throwErrno "Network.Socket.SendFile.Darwin"++-- | low-level wrapper around sendfile+-- non-blocking+-- returns number of bytes written and if EAGAIN/EINTR+-- does not call 'threadWaitWrite'+sendfile :: Fd -> Fd -> Int64 -> Int64 -> IO (Bool, Int64)+sendfile out_fd in_fd off count =+    alloca $ \len ->+        do poke len count+           sendfileI out_fd in_fd off len++-- NOTE: should we retry automatically on EINTR (but not EAGAIN)+sendfileI :: Fd -> Fd -> Int64 -> Ptr Int64 -> IO (Bool, Int64)+sendfileI out_fd in_fd off len =+    do status <- c_sendfile out_fd in_fd off len+       if (status == 0)+          then do nsent <- peek len+                  return $ (False, nsent)+          else do errno <- getErrno+                  if (errno == eAGAIN) || (errno == eINTR)+                   then do nsent <- peek len+                           return (True, nsent)+                   else throwErrno "Network.Socket.SendFile.Darwin.sendfileI"++safeMinus :: (Ord a, Num a) => a -> a -> a+safeMinus x y+    | y >= x = 0+    | otherwise = x - y  -- max num of bytes in one send maxBytes :: Int64
src/Network/Socket/SendFile/FreeBSD.hs view
@@ -1,38 +1,67 @@ {-# LANGUAGE ForeignFunctionInterface #-} -- | FreeBSD system-dependent code for 'sendfile'.-module Network.Socket.SendFile.FreeBSD (_sendFile) where-import Control.Concurrent (threadWaitWrite)+module Network.Socket.SendFile.FreeBSD (_sendFile, sendFileIter, sendfile) where+import Data.Int (Int64) import Foreign.C.Error (eAGAIN, eINTR, getErrno, throwErrno)-import Foreign.C.Types (CInt, CSize)+import Foreign.C.Types (CInt(..), CSize(..)) import Foreign.Marshal.Alloc (alloca) import Foreign.Ptr (Ptr, nullPtr) import Foreign.Storable (peek)-import System.Posix.Types (COff, Fd)+import Network.Socket.SendFile.Iter (Iter(..), runIter)+import System.Posix.Types (COff(..), Fd(..)) -_sendFile :: Fd -> Fd -> Integer -> Integer -> IO ()-_sendFile out_fd in_fd off count = -    alloca $ \sbytes -> do-      rsendfile out_fd in_fd (fromIntegral off) (fromIntegral count) sbytes+-- | automatically loop and send everything+_sendFile :: Fd -> Fd -> Int64 -> Int64 -> IO ()+_sendFile out_fd in_fd off count =+    do _ <- runIter (sendFileIter out_fd in_fd (fromIntegral count) (fromIntegral off) (fromIntegral count)) -- set blockSize == count. ie. send it all if we can.+       return () -rsendfile :: Fd -> Fd -> COff -> CSize -> Ptr COff -> IO ()-rsendfile _ _ _ 0 _ = return ()-rsendfile out_fd in_fd off remaining sbytes =-    do let bytes = min remaining maxBytes-       sent <- sendfile out_fd in_fd off bytes sbytes 0-       rsendfile out_fd in_fd (off + sent) (remaining `safeMinus` (fromIntegral  sent)) sbytes+sendFileIter  :: Fd -- ^ file descriptor corresponding to network socket+              -> Fd -- ^ file descriptor corresponding to file+              -> Int64 -- ^ maximum number of bytes to send at once+              -> Int64 -- ^ offset into file+              -> Int64 -- ^ total number of bytes to send+              -> IO Iter+sendFileIter out_fd in_fd blockSize off count =+    sendFileIterI out_fd in_fd (min (fromIntegral blockSize) maxBytes) (fromIntegral off) (fromIntegral count) -sendfile :: Fd -> Fd -> COff -> CSize -> Ptr COff -> COff -> IO COff-sendfile out_fd in_fd off count sbytes totalSent =-    do threadWaitWrite out_fd-       res <- c_sendfile_freebsd in_fd out_fd off count nullPtr sbytes 0-       if (res == 0)-          then do nbytes <- peek sbytes-                  return (totalSent + nbytes)+sendFileIterI :: Fd -- ^ file descriptor corresponding to network socket+              -> Fd -- ^ file descriptor corresponding to file+              -> CSize -- ^ maximum number of bytes to send at once+              -> COff -- ^ offset into file+              -> CSize -- ^ total number of bytes to send+              -> IO Iter+sendFileIterI _out_fd _in_fd _blockSize _off  0         = return (Done 0)+sendFileIterI  out_fd  in_fd  blockSize  off  remaining =+    do let bytes = min remaining blockSize+       (wouldBlock, nsent) <- alloca $ \sbytes -> sendfileI out_fd in_fd off bytes sbytes+       let cont = sendFileIterI out_fd in_fd blockSize (off + nsent) (remaining `safeMinus` (fromIntegral nsent))+       case wouldBlock of+         True  -> return (WouldBlock (fromIntegral nsent) out_fd cont)+         False -> return (Sent (fromIntegral nsent) cont)++-- | low-level wrapper around sendfile+-- non-blocking+-- returns number of bytes written and if EAGAIN+-- does not call 'threadWaitWrite'+sendfile :: Fd -> Fd -> Int64 -> Int64 -> IO (Bool, Int64)+sendfile out_fd in_fd off count =+    alloca $ \sbytes ->+        do (wb, sent) <- sendfileI out_fd in_fd (fromIntegral off) (fromIntegral count) sbytes+           return (wb, fromIntegral sent)++-- NOTE: should we retry automatically on EINTR (but not EAGAIN)+sendfileI :: Fd -> Fd -> COff -> CSize -> Ptr COff -> IO (Bool, COff)+sendfileI out_fd in_fd off count sbytes =+    do status <- c_sendfile out_fd in_fd off count sbytes+       if (status == 0)+          then do nsent <- peek sbytes+                  return (False, nsent)           else do errno <- getErrno-                  if (errno == eAGAIN) || (errno == eINTR) -                   then do nbytes <- peek sbytes-                           sendfile out_fd in_fd (off + nbytes) (count `safeMinus` (fromIntegral nbytes)) sbytes (totalSent + nbytes)-                   else throwErrno "Network.Socket.SendFile.FreeBSD.sendfile"+                  if (errno == eAGAIN) || (errno == eINTR)+                   then do nsent <- peek sbytes+                           return (True, nsent)+                   else throwErrno "Network.Socket.SendFile.FreeBSD.sendfileI"  safeMinus :: (Ord a, Num a) => a -> a -> a safeMinus x y@@ -45,3 +74,6 @@  foreign import ccall unsafe "sys/uio.h sendfile" c_sendfile_freebsd     :: Fd -> Fd -> COff -> CSize -> Ptr () -> Ptr COff -> CInt -> IO CInt++c_sendfile :: Fd -> Fd -> COff -> CSize -> Ptr COff -> IO CInt+c_sendfile out_fd in_fd off count sbytes = c_sendfile_freebsd in_fd out_fd off count nullPtr sbytes 0
+ src/Network/Socket/SendFile/Handle.hs view
@@ -0,0 +1,76 @@+-- | Handle-based versions of some of the functions exported by+-- Network.Socket.SendFile. +module Network.Socket.SendFile.Handle (+  ByteCount,+  Offset,+  Iter(..), runIter,+  -- * Handle-based sendFiles+  sendFile,+  sendFileIterWith,+  sendFile',+  sendFileIterWith'+  ) where++import System.IO (Handle, hFileSize)++import qualified Network.Socket.SendFile.Internal as Internal+import Network.Socket.SendFile.Iter (Iter(..), runIter)+import Network.Socket.SendFile (ByteCount, Offset)+import Network.Socket (Socket)++-- | Simple sendFile - give it a Socket and a Handle, and it sends the entire+-- file through the socket.+--+-- WARNING: This function will raise 'IOError' 'IllegalOperation'+-- if the 'Handle' is not for an 'Fd'.+sendFile+  :: Socket+  -> Handle+  -> IO ()+sendFile outs inh = do+  count <- hFileSize inh+  Internal.sendFile'' outs inh 0 count++-- | A more interactive version of sendFile, which accepts a callback function+-- in addition to the socket and handle.  The callback will be called for each+-- chunk of data the sendFileIterWith function acts on.+--+-- WARNING: This function will raise 'IOError' 'IllegalOperation'+-- if the 'Handle' is not for an 'Fd'.+sendFileIterWith+  :: (IO Iter -> IO a)+  -> Socket+  -> Handle+  -> ByteCount+  -> IO a+sendFileIterWith stepper outs inh blockSize = do+  count <- hFileSize inh+  Internal.sendFileIterWith'' stepper outs inh blockSize 0 count++-- | A sendFile that allows the user to send a subset of the file associated+-- with the given handle.+--+-- WARNING: This function will raise 'IOError' 'IllegalOperation'+-- if the 'Handle' is not for an 'Fd'.+sendFile'+  :: Socket+  -> Handle+  -> Offset+  -> ByteCount+  -> IO ()+sendFile' = Internal.sendFile''++-- | A more powerful version of sendFileIterWith, which allows the sending of a+-- subset of the given file.+--+-- WARNING: This function will raise 'IOError' 'IllegalOperation'+-- if the 'Handle' is not for an 'Fd'.+sendFileIterWith'+  :: (IO Iter -> IO a)+  -> Socket+  -> Handle+  -> ByteCount+  -> Offset+  -> ByteCount+  -> IO a+sendFileIterWith' = Internal.sendFileIterWith''
src/Network/Socket/SendFile/Internal.hs view
@@ -1,124 +1,175 @@ {-# LANGUAGE CPP, RecordWildCards #-} module Network.Socket.SendFile.Internal (     sendFile,+    sendFileIterWith,     sendFile',+    sendFileIterWith',+    sendFile'',+    sendFileIterWith'',     unsafeSendFile,+    unsafeSendFileIterWith,     unsafeSendFile',+    unsafeSendFileIterWith',     sendFileMode,     ) where+ #if defined(PORTABLE_SENDFILE)-import Data.ByteString.Char8 (hGet, hPutStr, length, ByteString)-import Network.Socket.ByteString (sendAll)-import Network.Socket (Socket(..))-import Prelude hiding (length)-import System.IO (Handle, IOMode(..), SeekMode(..), hFileSize, hSeek, withBinaryFile)+import Network.Socket.SendFile.Portable (sendFileMode, sendFile'', sendFileIterWith'', unsafeSendFile'', unsafeSendFileIterWith'') #else-import Network.Socket (Socket(..), fdSocket)-import System.IO (Handle, IOMode(..), hFileSize, withBinaryFile)+import Network.Socket (fdSocket)+import Network.Socket.SendFile.Util import System.Posix.Types (Fd(..))-#endif #ifdef __GLASGOW_HASKELL__ #if __GLASGOW_HASKELL__ >= 611-import GHC.IO.Handle.Internals (withHandle, flushWriteBuffer)-import GHC.IO.Handle.Types (Handle__(..), HandleType(..))+import GHC.IO.Handle.Internals (withHandle_)+import GHC.IO.Handle.Types (Handle__(..)) import qualified GHC.IO.FD as FD -- import qualified GHC.IO.Handle.FD as FD import GHC.IO.Exception import Data.Typeable (cast)-import System.IO (hFlush)-import System.IO.Error #else import GHC.IOBase import GHC.Handle hiding (fdToHandle) import qualified GHC.Handle #endif #endif+#endif +import Network.Socket (Socket)+import Network.Socket.SendFile.Iter (Iter(..))+import System.IO (Handle, IOMode(..), hFileSize, hFlush, withBinaryFile)+#ifdef __GLASGOW_HASKELL__+#if __GLASGOW_HASKELL__ >= 611+import System.IO.Error+#endif+#endif + #if defined(WIN32_SENDFILE)-import Network.Socket.SendFile.Win32 (_sendFile)+import Network.Socket.SendFile.Win32 (_sendFile, sendFileIter)  sendFileMode :: String sendFileMode = "WIN32_SENDFILE" #endif  #if defined(LINUX_SENDFILE)-import Network.Socket.SendFile.Linux (_sendFile)+import Network.Socket.SendFile.Linux (_sendFile, sendFileIter)  sendFileMode :: String sendFileMode = "LINUX_SENDFILE" #endif  #if defined(FREEBSD_SENDFILE)-import Network.Socket.SendFile.FreeBSD (_sendFile)+import Network.Socket.SendFile.FreeBSD (_sendFile, sendFileIter)  sendFileMode :: String sendFileMode = "FREEBSD_SENDFILE" #endif  #if defined(DARWIN_SENDFILE)-import Network.Socket.SendFile.Darwin (_sendFile)+import Network.Socket.SendFile.Darwin (_sendFile, sendFileIter)  sendFileMode :: String sendFileMode = "DARWIN_SENDFILE" #endif  #if defined(PORTABLE_SENDFILE)-sendFileMode :: String-sendFileMode = "PORTABLE_SENDFILE" +#else sendFile'' :: Socket -> Handle -> Integer -> Integer -> IO ()-sendFile'' = wrapSendFile' $ \outs inp off count -> do-    hSeek inp AbsoluteSeek off-    rsend (sendAll outs) inp count--unsafeSendFile'' :: Handle -> Handle -> Integer -> Integer -> IO ()-unsafeSendFile'' = wrapSendFile' $ \outp inp off count -> do-    hSeek inp AbsoluteSeek off-    rsend (hPutStr outp) inp count-    hFlush outp -- match the behavior that all data is "flushed to the os" of native implementations+sendFile'' outs inh off count =+#if MIN_VERSION_network(3,0,0)+    do out_fd <- fmap Fd (fdSocket outs)+#else+    do let out_fd = Fd (fdSocket outs)+#endif+       withFd inh $ \in_fd ->+         wrapSendFile' (\out_fd_ in_fd_ _blockSize_ off_ count_ -> _sendFile out_fd_ in_fd_ off_ count_)+                       out_fd in_fd count off count -rsend :: (ByteString -> IO ()) -> Handle -> Integer -> IO ()-rsend write inp n = do-  loop n-  where-    loop 0        = return ()-    loop reqBytes = do-      let bytes = min 32768 reqBytes :: Integer-      buf <- hGet inp (fromIntegral bytes)-      write buf-      loop $ reqBytes - (fromIntegral $ length buf)+sendFileIterWith'' :: (IO Iter -> IO a) -> Socket -> Handle -> Integer -> Integer -> Integer -> IO a+sendFileIterWith'' stepper outs inp blockSize off count =+#if MIN_VERSION_network(3,0,0)+    do out_fd <- fmap Fd (fdSocket outs) #else-sendFile'' :: Socket -> Handle -> Integer -> Integer -> IO ()-sendFile'' outs inp off count =     do let out_fd = Fd (fdSocket outs)-       in_fd <- handleToFd inp-       wrapSendFile' _sendFile out_fd in_fd off count+#endif+       withFd inp $ \in_fd ->+         stepper $ wrapSendFile' sendFileIter out_fd in_fd blockSize off count + unsafeSendFile'' :: Handle -> Handle -> Integer -> Integer -> IO () unsafeSendFile'' outp inp off count =     do hFlush outp-       out_fd <- handleToFd outp-       in_fd  <- handleToFd inp-       wrapSendFile' _sendFile out_fd in_fd off count-    +       withFd outp $ \out_fd ->+         withFd inp $ \in_fd ->+          wrapSendFile' (\out_fd_ in_fd_ _blockSize_ off_ count_ -> _sendFile out_fd_ in_fd_ off_ count_)+                        out_fd in_fd count off count+--            wrapSendFile' _sendFile out_fd in_fd off count++unsafeSendFileIterWith'' :: (IO Iter -> IO a) -> Handle -> Handle -> Integer -> Integer -> Integer -> IO a+unsafeSendFileIterWith'' stepper outp inp blockSize off count =+    do hFlush outp+       withFd outp $ \out_fd ->+         withFd inp $ \in_fd ->+             stepper $ wrapSendFile' sendFileIter out_fd in_fd blockSize off count++-- The Fd should not be used after the action returns because the+-- Handler may be garbage collected and than will cause the finalizer+-- to close the fd.+withFd :: Handle -> (Fd -> IO a) -> IO a+#ifdef __GLASGOW_HASKELL__+#if __GLASGOW_HASKELL__ >= 611+withFd h f = withHandle_ "withFd" h $ \ Handle__{..} -> do+  case cast haDevice of+    Nothing -> ioError (ioeSetErrorString (mkIOError IllegalOperation+                                           "withFd" (Just h) Nothing)+                        "handle is not a file descriptor")+    Just fd -> f (Fd (fromIntegral (FD.fdFD fd)))+#else+withFd h f =+    withHandle_ "withFd" h $ \ h_ ->+      f (Fd (fromIntegral (haFD h_))) #endif+#endif ++#endif+ sendFile :: Socket -> FilePath -> IO ()-sendFile outs infp = withBinaryFile infp ReadMode $ \inp -> do-    count <- hFileSize inp-    sendFile'' outs inp 0 count+sendFile outs infp =+    withBinaryFile infp ReadMode $ \inp -> do+      count <- hFileSize inp+      sendFile'' outs inp 0 count +sendFileIterWith :: (IO Iter -> IO a) -> Socket -> FilePath -> Integer -> IO a+sendFileIterWith stepper outs infp blockSize =+    withBinaryFile infp ReadMode $ \inp -> do+      count <- hFileSize inp+      sendFileIterWith'' stepper outs inp blockSize 0 count+ sendFile' :: Socket -> FilePath -> Integer -> Integer -> IO () sendFile' outs infp offset count =     withBinaryFile infp ReadMode $ \inp ->         sendFile'' outs inp offset count +sendFileIterWith' :: (IO Iter -> IO a) -> Socket -> FilePath -> Integer -> Integer -> Integer -> IO a+sendFileIterWith' stepper outs infp blockSize offset count =+    withBinaryFile infp ReadMode $ \inp ->+        sendFileIterWith'' stepper outs inp blockSize offset count+ unsafeSendFile :: Handle -> FilePath -> IO ()-unsafeSendFile outp infp = +unsafeSendFile outp infp =     withBinaryFile infp ReadMode $ \inp -> do       count <- hFileSize inp       unsafeSendFile'' outp inp 0 count +unsafeSendFileIterWith :: (IO Iter -> IO a) -> Handle -> FilePath -> Integer -> IO a+unsafeSendFileIterWith stepper outp infp blockSize =+    withBinaryFile infp ReadMode $ \inp -> do+      count <- hFileSize inp+      unsafeSendFileIterWith'' stepper outp inp blockSize 0 count++ unsafeSendFile'     :: Handle    -- ^ The output handle     -> FilePath  -- ^ The input filepath@@ -128,29 +179,15 @@ unsafeSendFile' outp infp offset count =     withBinaryFile infp ReadMode $ \inp -> do       unsafeSendFile'' outp inp offset count-          --- | wraps sendFile' to check arguments-wrapSendFile' :: Integral i => (a -> b -> i -> i -> IO ()) -> a -> b -> Integer -> Integer -> IO ()-wrapSendFile' fun outp inp off count-    | off < 0    = error "SendFile - offset must be a positive integer"-    | count < 0  = error "SendFile - count must be a positive integer"-    | count == 0 = return () -- Send nothing -- why do the work? Also, Windows and FreeBSD treat '0' as 'send the whole file'.-    | otherwise  = fun outp inp (fromIntegral off) (fromIntegral count) -#if !defined(PORTABLE_SENDFILE)-handleToFd :: Handle -> IO Fd-#ifdef __GLASGOW_HASKELL__-#if __GLASGOW_HASKELL__ >= 611-handleToFd h = withHandle "handleToFd" h $ \ Handle__{..} -> do-  case cast haDevice of-    Nothing -> ioError (ioeSetErrorString (mkIOError IllegalOperation-                                           "handleToFd" (Just h) Nothing) -                        "handle is not a file descriptor")-    Just fd -> do-     return (Handle__{..} , Fd (fromIntegral (FD.fdFD fd)))-#else-handleToFd h = withHandle "handleToFd" h $ \ h_ -> do-  return (h_, Fd (fromIntegral (haFD h_)))-#endif-#endif-#endif+unsafeSendFileIterWith'+    :: (IO Iter -> IO a)+    -> Handle    -- ^ The output handle+    -> FilePath  -- ^ The input filepath+    -> Integer   -- ^ maximum block size+    -> Integer   -- ^ The offset to start at+    -> Integer   -- ^ The number of bytes to send+    -> IO a+unsafeSendFileIterWith' stepper outp infp blockSize offset count =+    withBinaryFile infp ReadMode $ \inp -> do+      unsafeSendFileIterWith'' stepper outp inp blockSize offset count
+ src/Network/Socket/SendFile/Iter.hs view
@@ -0,0 +1,108 @@+module Network.Socket.SendFile.Iter where++import Control.Concurrent (threadWaitWrite)+import Data.Int           (Int64)+import System.Posix.Types (Fd)++-- | An iteratee for sendfile+--+-- In general, a whole file is not sent by a single call to+-- sendfile(), but a series of calls which send successive pieces.+--+-- The high-level API in this sendfile library has calls which will+-- send the entire file (or an entire requested offset+length), before+-- returning.+--+-- However, there are instances where you want to be a bit more+-- involved in the sending loop. For example, if you want to tickle a+-- timeout after each chunk is sent or update a progress bar.+--+-- The 'Iter' type gives you that power with out requiring you to+-- manage all the low-level details of the sendfile loop. The+-- interface is simple and consistant across all platforms.+--+-- A call to sendfile() can result in three different states:+--+--  (1) the requested number of bytes for that iteration was sent+--  successfully, there are more bytes left to send.+--+--  (2) some (possibly 0) bytes were sent, but the file descriptor+--  would now block if more bytes were written. There are more bytes+--  left to send.+--+--  (2) All the bytes were sent, and there is nothing left to send.+--+-- We handle these three cases by using a type with three+-- constructors:+-- +-- @+--  data Iter+--      = Sent       Int64    (IO Iter)+--      | WouldBlock Int64 Fd (IO Iter)+--      | Done       Int64             +-- @+--+-- All three constructors provide an 'Int64' which represents the+-- number of bytes sent for that particular iteration. (Not the total+-- byte count).+--+-- The 'Sent' and 'WouldBlock' constructors provide 'IO' 'Iter' as their+-- final argument. Running this IO action will send the next block of+-- data.+--+-- The 'WouldBlock' constructor also provides the 'Fd' for the output+-- socket. You should not send anymore data until the 'Fd' would not+-- block. The easiest way to do that is to use 'threadWaitWrite' to+-- suspend the thread until the 'Fd' is available.+--+-- A very simple function to drive the Iter might look like:+-- +-- @+-- runIter :: IO Iter -> IO ()+-- runIter iter =+--    do r <- iter+--       case r of+--         (Done _n)      -> return ()+--         (Sent _n cont) -> runIter cont+--         (WouldBlock _n fd cont) -> +--             do threadWaitWrite fd+--                runIter cont+-- @+--+-- You would use it as the first argument to a *IterWith function, e.g.+--+-- @+--  sendFileIterWith runIter outputSocket \"\/path\/to\/file\" 2^16 +-- @+--+-- The 'runIter' function provided by this module is similar, but also returns the total number of bytes sent.+--+-- NOTE: You must not use the 'Fd' or the 'IO' 'Iter' after the call+-- to *IterWith has returned. When the *IterWith functions return,+-- the file descriptors may be closed due to finalizers running.+data Iter+    = Sent       Int64    (IO Iter) -- ^ number of bytes sent this pass and a continuation to send more+    | WouldBlock Int64 Fd (IO Iter) -- ^ number of bytes sent, Fd that blocked, continuation to send more. NOTE: The Fd should not be used outside the running of the Iter as it may be freed when the Iter is done+    | Done       Int64              -- ^ number of bytes sent, no more to send++-- | A simple function to drive the *IterWith functions.+-- It returns the total number of bytes sent.+runIter :: IO Iter -> IO Int64+runIter = runIter' 0+    where+      runIter' :: Int64 -> IO Iter -> IO Int64+      runIter' acc iter =+          do r <- iter+             case r of+               (Sent n cont) -> +                   do let acc' = (acc + n) +--                      putStrLn $ "Sent " ++ show acc'+                      acc' `seq` runIter' acc' cont+               (Done n) ->+                   do -- putStrLn $ "Done " ++ show (acc + n)+                      return (acc + n)+               (WouldBlock n fd cont) -> +                   do threadWaitWrite fd+                      let acc' = (acc + n) +--                      putStrLn $ "WouldBlock " ++ (show acc')+                      acc' `seq` runIter' acc' cont
src/Network/Socket/SendFile/Linux.hsc view
@@ -1,46 +1,84 @@ {-# LANGUAGE ForeignFunctionInterface #-} -- | Linux system-dependent code for 'sendfile'.-module Network.Socket.SendFile.Linux (_sendFile) where-import Data.Int-import Data.Word+module Network.Socket.SendFile.Linux (_sendFile, sendFileIter, sendfile) where++import Data.Int (Int32, Int64)    -- Int64 is imported on 64-bit systems+import Data.Word (Word32, Word64) -- Word64 is imported on 64-bit systems+import Foreign.C (CInt(..)) import Foreign.C.Error (eAGAIN, getErrno, throwErrno) import Foreign.Marshal (alloca) import Foreign.Ptr (Ptr) import Foreign.Storable(poke)-import System.Posix.Types (Fd)-import Control.Concurrent (threadWaitWrite)-+import Network.Socket.SendFile.Iter (Iter(..), runIter)+import System.Posix.Types (Fd(..)) #include <sys/sendfile.h>+#include <stdio.h> +-- | automatically loop and send everything _sendFile :: Fd -> Fd -> Int64 -> Int64 -> IO ()-_sendFile out_fd in_fd off count = do-    alloca $ \poff -> do-    poke poff off-    rsendfile out_fd in_fd poff count+_sendFile out_fd in_fd off count = +    do _ <- runIter (sendFileIter out_fd in_fd count off count) -- set blockSize == count. ie. send it all if we can.+       return () -rsendfile :: Fd -> Fd -> Ptr Int64 -> Int64 -> IO ()-rsendfile _      _     _    0         = return ()-rsendfile out_fd in_fd poff remaining = do-    let bytes = min remaining maxBytes-    sbytes <- sendfile out_fd in_fd poff bytes-    rsendfile out_fd in_fd poff (remaining - sbytes)-    -sendfile :: Fd -> Fd -> Ptr Int64 -> Int64 -> IO Int64-sendfile out_fd in_fd poff bytes = do-    threadWaitWrite out_fd-    sbytes <- c_sendfile out_fd in_fd poff (fromIntegral bytes)+-- | a way to send things in chunks+sendFileIter :: Fd -- ^ file descriptor corresponding to network socket+             -> Fd -- ^ file descriptor corresponding to file+             -> Int64 -- ^ maximum number of bytes to send at once+             -> Int64 -- ^ offset into file+             -> Int64 -- ^ total number of bytes to send+             -> IO Iter+sendFileIter out_fd in_fd blockSize off remaining =+--    alloca $ \poff -> +--        do poke poff off+           sendFileIterI out_fd in_fd (min blockSize maxBytes) off remaining++sendFileIterI :: Fd -- ^ file descriptor corresponding to network socket+              -> Fd -- ^ file descriptor corresponding to file+              -> Int64 -- ^ maximum number of bytes to send at once+              -> Int64 -- ^ offset into file+              -> Int64     -- ^ total number of bytes to send+              -> IO Iter+sendFileIterI _out_fd _in_fd _blockSize _off  0         = return (Done 0)+sendFileIterI  out_fd  in_fd  blockSize  off  remaining =+    do let bytes = min remaining blockSize+       (wouldBlock, sbytes) <- sendfile out_fd in_fd off bytes+       let cont = sendFileIterI out_fd in_fd blockSize (off + sbytes) (remaining `safeMinus` sbytes)+       case wouldBlock of+         True  -> return (WouldBlock sbytes out_fd cont)+         False -> return (Sent sbytes cont)++-- | low-level wrapper around sendfile+-- non-blocking+-- returns number of bytes written and whether the fd would block (aka, EAGAIN)+-- does not call 'threadWaitWrite'+sendfile :: Fd -> Fd -> Int64 -> Int64 -> IO (Bool, Int64)+sendfile out_fd in_fd off bytes = +    alloca $ \poff -> +        do poke poff off+           sendfileI out_fd in_fd poff bytes++-- low-level wrapper around linux sendfile+sendfileI :: Fd -> Fd -> Ptr Int64 -> Int64 -> IO (Bool, Int64)+sendfileI out_fd in_fd poff bytes = do+    sbytes <- {-# SCC "c_sendfile" #-} c_sendfile out_fd in_fd poff (fromIntegral bytes)     if sbytes <= -1       then do errno <- getErrno               if errno == eAGAIN-                then sendfile out_fd in_fd poff bytes-                else throwErrno "Network.Socket.SendFile.Linux"-      else return (fromIntegral sbytes)+                then return (True, 0)+                else throwErrno "Network.Socket.SendFile.Linux.sendfileI"+      else return (False, fromIntegral sbytes) +safeMinus :: (Ord a, Num a, Show a) => a -> a -> a+safeMinus x y+    | y > x = error $ "y > x " ++ show (y,x)+    | otherwise = x - y++ -- max num of bytes in one send maxBytes :: Int64 maxBytes = fromIntegral (maxBound :: (#type ssize_t))  -- sendfile64 gives LFS support foreign import ccall unsafe "sendfile64" c_sendfile-    :: Fd -> Fd -> Ptr (#type off_t) -> (#type size_t) -> IO (#type ssize_t)+    :: Fd -> Fd -> Ptr (#type off64_t) -> (#type size_t) -> IO (#type ssize_t) 
+ src/Network/Socket/SendFile/Portable.hs view
@@ -0,0 +1,191 @@+{-# LANGUAGE CPP #-}+module Network.Socket.SendFile.Portable +     ( sendFile+     , sendFileIterWith+     , sendFile'+     , sendFileIterWith'+     , sendFile''+     , sendFileIterWith''+     , unsafeSendFile+     , unsafeSendFileIterWith+     , unsafeSendFile'+     , unsafeSendFile''+     , unsafeSendFileIterWith'+     , unsafeSendFileIterWith''+     , sendFileMode+     )+    where++import Data.ByteString.Char8 (hGet, hPut, length, ByteString)+import qualified Data.ByteString.Char8 as C+import Network.Socket.ByteString (send)+import Network.Socket (Socket(..), fdSocket)+import Network.Socket.SendFile.Iter (Iter(..), runIter)+import Network.Socket.SendFile.Util (wrapSendFile')+import Prelude hiding (length)+import System.IO (Handle, IOMode(..), SeekMode(..), hFileSize, hFlush, hIsEOF, hSeek, withBinaryFile)+import System.Posix.Types (Fd(..))+#ifdef __GLASGOW_HASKELL__+#if __GLASGOW_HASKELL__ >= 611+import System.IO.Error+#endif+#endif +++sendFileMode :: String+sendFileMode = "PORTABLE_SENDFILE"++sendFileIterWith'' :: (IO Iter -> IO a) -> Socket -> Handle -> Integer -> Integer -> Integer -> IO a+sendFileIterWith'' stepper =+    wrapSendFile' $ \outs inp blockSize off count ->+        do hSeek inp AbsoluteSeek off+           stepper (sendFileIterS outs inp blockSize {- off -} count Nothing)++sendFile'' :: Socket -> Handle -> Integer -> Integer -> IO ()+sendFile'' outs inh off count =+    do _ <- sendFileIterWith'' runIter outs inh count off count+       return ()++unsafeSendFileIterWith'' :: (IO Iter -> IO a) -> Handle -> Handle -> Integer -> Integer -> Integer -> IO a+unsafeSendFileIterWith'' stepper =+    wrapSendFile' $ \outp inp blockSize off count ->+        do hSeek inp AbsoluteSeek off+           a <- stepper (unsafeSendFileIter outp inp blockSize count Nothing)+           hFlush outp+           return a++unsafeSendFile'' :: Handle -> Handle -> Integer -> Integer -> IO ()+unsafeSendFile'' outh inh off count =+    do _ <- unsafeSendFileIterWith'' runIter outh inh count off count+       return ()++sendFileIterS :: Socket  -- ^ output network socket+             -> Handle  -- ^ input handle+             -> Integer -- ^ maximum number of bytes to send at once+             -> Integer -- ^ total number of bytes to send+             -> Maybe ByteString+             -> IO Iter+sendFileIterS _socket _inh _blockSize {- _off -} 0        _    = return (Done 0)+sendFileIterS socket   inh  blockSize {- off -} remaining mBuf =+    do buf <- nextBlock+       nsent <- send socket buf+       let leftOver =+               if nsent < (C.length buf)+                  then Just (C.drop nsent buf)+                  else Nothing+       let cont = sendFileIterS socket inh blockSize {- (off + (fromIntegral nsent)) -} (remaining `safeMinus` (fromIntegral nsent)) leftOver+       if nsent < (length buf)+#if MIN_VERSION_network(3,0,0)+          then do fd <- fdSocket socket+                  return (WouldBlock (fromIntegral nsent) (Fd fd) cont)+#else+          then return (WouldBlock (fromIntegral nsent) (Fd $ fdSocket socket) cont)+#endif+          else return (Sent       (fromIntegral nsent)                        cont)+    where+   nextBlock =+          case mBuf of+            (Just b) -> return b+            Nothing ->+                do eof <- hIsEOF inh+                   if eof+                    then ioError (mkIOError eofErrorType ("Reached EOF but was hoping to read " ++ show remaining ++ " more byte(s).") (Just inh) Nothing)+                    else do let bytes = min 32768 (min blockSize remaining)+                            hGet inh (fromIntegral bytes) -- we could check that we got fewer bytes than requested here, but we will send what we got and catch the EOF next time around++safeMinus :: (Show a, Ord a, Num a) => a -> a -> a+safeMinus x y+    | y > x = error $ "y > x " ++ show (y,x)+    | otherwise = x - y++unsafeSendFileIter :: Handle  -- ^ output handle+                   -> Handle  -- ^ input handle+                   -> Integer -- ^ maximum number of bytes to send at once+--                   -> Integer -- ^ offset into file+                   -> Integer -- ^ total number of bytes to send+                   -> Maybe ByteString+                   -> IO Iter+unsafeSendFileIter _outh _inh _blockSize 0         _mBuf = return (Done 0)+unsafeSendFileIter  outh  inh  blockSize remaining  mBuf =+    do buf <- nextBlock+       hPut outh buf -- eventually this should use a non-blocking version of hPut+       let nsent = length buf+{-+           leftOver =+               if nsent < (C.length buf)+                  then Just (C.drop nsent buf)+                  else Nothing+-}+           cont = unsafeSendFileIter outh inh blockSize {- (off + (fromIntegral nsent)) -} (remaining - (fromIntegral nsent)) Nothing+       if nsent < (length buf)+          then do error "unsafeSendFileIter: internal error" -- return (WouldBlock (fromIntegral nsent) (Fd $ fdSocket socket) cont)+          else return (Sent (fromIntegral nsent) cont)+    where+      nextBlock =+          case mBuf of+            (Just b) -> return b+            Nothing ->+                do eof <- hIsEOF inh+                   if eof+                    then ioError (mkIOError eofErrorType ("Reached EOF but was hoping to read " ++ show remaining ++ " more byte(s).") (Just inh) Nothing)+                    else do let bytes = min 32768 (min blockSize remaining)+                            hGet inh (fromIntegral bytes) -- we could check that we got fewer bytes than requested here, but we will send what we got and catch the EOF next time around++-- copied from Internal.hs -- not sure how to avoid having two copies of this code yet++sendFile :: Socket -> FilePath -> IO ()+sendFile outs infp =+    withBinaryFile infp ReadMode $ \inp -> do+      count <- hFileSize inp+      sendFile'' outs inp 0 count++sendFileIterWith :: (IO Iter -> IO a) -> Socket -> FilePath -> Integer -> IO a+sendFileIterWith stepper outs infp blockSize =+    withBinaryFile infp ReadMode $ \inp -> do+      count <- hFileSize inp+      sendFileIterWith'' stepper outs inp blockSize 0 count++sendFile' :: Socket -> FilePath -> Integer -> Integer -> IO ()+sendFile' outs infp offset count =+    withBinaryFile infp ReadMode $ \inp ->+        sendFile'' outs inp offset count++sendFileIterWith' :: (IO Iter -> IO a) -> Socket -> FilePath -> Integer -> Integer -> Integer -> IO a+sendFileIterWith' stepper outs infp blockSize offset count =+    withBinaryFile infp ReadMode $ \inp ->+        sendFileIterWith'' stepper outs inp blockSize offset count++unsafeSendFile :: Handle -> FilePath -> IO ()+unsafeSendFile outp infp =+    withBinaryFile infp ReadMode $ \inp -> do+      count <- hFileSize inp+      unsafeSendFile'' outp inp 0 count++unsafeSendFileIterWith :: (IO Iter -> IO a) -> Handle -> FilePath -> Integer -> IO a+unsafeSendFileIterWith stepper outp infp blockSize =+    withBinaryFile infp ReadMode $ \inp -> do+      count <- hFileSize inp+      unsafeSendFileIterWith'' stepper outp inp blockSize 0 count+++unsafeSendFile'+    :: Handle    -- ^ The output handle+    -> FilePath  -- ^ The input filepath+    -> Integer    -- ^ The offset to start at+    -> Integer -- ^ The number of bytes to send+    -> IO ()+unsafeSendFile' outp infp offset count =+    withBinaryFile infp ReadMode $ \inp -> do+      unsafeSendFile'' outp inp offset count++unsafeSendFileIterWith'+    :: (IO Iter -> IO a)+    -> Handle    -- ^ The output handle+    -> FilePath  -- ^ The input filepath+    -> Integer   -- ^ maximum block size+    -> Integer   -- ^ The offset to start at+    -> Integer   -- ^ The number of bytes to send+    -> IO a+unsafeSendFileIterWith' stepper outp infp blockSize offset count =+    withBinaryFile infp ReadMode $ \inp -> do+      unsafeSendFileIterWith'' stepper outp inp blockSize offset count
+ src/Network/Socket/SendFile/Util.hs view
@@ -0,0 +1,12 @@+module Network.Socket.SendFile.Util+    ( wrapSendFile'+    ) where++-- | wraps sendFile' to check arguments+wrapSendFile' :: Integral i => (a -> b -> i -> i -> i -> IO c) -> a -> b -> Integer -> Integer -> Integer -> IO c+wrapSendFile' fun outp inp blockSize off count+--    | count     == 0 = return () -- Send nothing -- why do the work? Also, Windows and FreeBSD treat '0' as 'send the whole file'.+    | count     <  0 = error "SendFile - count must be a positive integer"+    | (count /= 0) && (blockSize <= 0) = error "SendFile - blockSize must be a positive integer greater than 1"+    | off       <  0 = error "SendFile - offset must be a positive integer"+    | otherwise      = fun outp inp (fromIntegral blockSize) (fromIntegral off) (fromIntegral count)
src/Network/Socket/SendFile/Win32.hsc view
@@ -1,14 +1,15 @@ {-# LANGUAGE ForeignFunctionInterface #-} -- | Win32 system-dependent code for 'TransmitFile'.-module Network.Socket.SendFile.Win32 (_sendFile) where+module Network.Socket.SendFile.Win32 (_sendFile, sendFileIter) where import Data.Bits ((.|.)) import Data.Int import Foreign.C.Error (throwErrnoIf)-import Foreign.C.Types (CInt)+import Foreign.C.Types (CInt(..)) import Foreign.Marshal.Alloc (alloca)-import Foreign.Ptr (IntPtr, Ptr, intPtrToPtr, nullPtr)+import Foreign.Ptr (IntPtr(..), Ptr, intPtrToPtr, nullPtr) import Foreign.Storable (peek)-import System.Posix.Types (Fd)+import Network.Socket.SendFile.Iter (Iter(..),runIter)+import System.Posix.Types (Fd(..)) import System.Win32.Types (DWORD, HANDLE, failIfZero)  #include <windows.h>@@ -17,10 +18,35 @@ type SOCKET = Fd  _sendFile :: Fd -> Fd -> Int64 -> Int64 -> IO ()-_sendFile out_fd in_fd off count = do-    in_hdl <- get_osfhandle in_fd-    rtransmitFile out_fd in_hdl off count+_sendFile out_fd in_fd off count =+    do _ <- runIter (sendFileIter out_fd in_fd (fromIntegral count) (fromIntegral off) (fromIntegral count)) -- set blockSize == count. ie. send it all if we can.+       return () +sendFileIter  :: Fd -- ^ file descriptor corresponding to network socket+              -> Fd -- ^ file descriptor corresponding to file+              -> Int64 -- ^ maximum number of bytes to send at once+              -> Int64 -- ^ offset into file+              -> Int64 -- ^ total number of bytes to send+              -> IO Iter+sendFileIter out_fd in_fd blockSize off count =+    do in_hdl <- get_osfhandle in_fd+       sendFileIterI out_fd in_hdl (min (fromIntegral blockSize) maxBytes) (fromIntegral off) (fromIntegral count)++sendFileIterI :: SOCKET -- ^ file descriptor corresponding to network socket+              -> HANDLE -- ^ file descriptor corresponding to file+              -> Int64 -- ^ maximum number of bytes to send at once+              -> Int64 -- ^ offset into file+              -> Int64 -- ^ total number of bytes to send+              -> IO Iter+sendFileIterI _out_fd _in_fd _blockSize _off  0         = return (Done 0)+sendFileIterI  out_fd  in_fd  blockSize  off  remaining =+    do let bytes = min remaining blockSize+       (wouldBlock, nsent) <- sendfileI out_fd in_fd off bytes+       let cont = sendFileIterI out_fd in_fd blockSize (off + nsent) (remaining `safeMinus` (fromIntegral nsent))+       case wouldBlock of+         True  -> return (WouldBlock (fromIntegral nsent) out_fd cont)+         False -> return (Sent (fromIntegral nsent) cont)+ get_osfhandle :: Fd        -- ^ User file descriptor.               -> IO HANDLE -- ^ The operating-system file handle. get_osfhandle fd = do@@ -36,25 +62,23 @@     -> DWORD     -- ^ the move method     -> IO Int64  -- ^ the new absolute offset setFilePointerEx hdl off meth = alloca $ \res -> do-    failIfZero+    _ <- failIfZero       "Network.Socket.SendFile.Win32.setFilePointerEx"       (c_SetFilePointerEx hdl off res meth)     peek res -rtransmitFile :: SOCKET -> HANDLE -> Int64 -> Int64 -> IO ()-rtransmitFile _      _      _   0         = return ()-rtransmitFile out_fd in_hdl off remaining = do-    let bytes = min remaining maxBytes-    setFilePointerEx in_hdl off (#const FILE_BEGIN)-    transmitFile out_fd in_hdl (fromIntegral bytes)-    rtransmitFile out_fd in_hdl (off + bytes) (remaining - bytes)+sendfileI :: SOCKET -> HANDLE -> Int64 -> Int64 -> IO (Bool, Int64)+sendfileI out_fd in_hdl off count =+    do _ <- setFilePointerEx in_hdl off (#const FILE_BEGIN)+       transmitFile out_fd in_hdl (fromIntegral count)+       return (False, count)  transmitFile :: SOCKET -- ^ A handle to a connected socket.              -> HANDLE -- ^ A handle to the open file that the TransmitFile function transmits.              -> DWORD  -- ^ The number of bytes in the file to transmit.              -> IO () transmitFile out_fd in_hdl count = do-    failIfZero+    _ <- failIfZero       "Network.Socket.SendFile.Win32.transmitFile"       (c_TransmitFile out_fd in_hdl count 0 nullPtr nullPtr (#{const TF_USE_KERNEL_APC} .|. #{const TF_WRITE_BEHIND}))     return ()@@ -62,6 +86,11 @@     --   If the TransmitFile function is called with the lpOverlapped parameter     --   set to NULL, the operation is executed as synchronous I/O. The function     --   will not complete until the file has been sent.++safeMinus :: (Ord a, Num a) => a -> a -> a+safeMinus x y+    | y >= x = 0+    | otherwise = x - y  -- max num of bytes in one send -- Windows will complain of an "invalid argument" if you use the maxBound of a DWORD, despite the fact that the count parameter is a DWORD; so the upper bound of a 32-bit integer seems to be the real limit, similar to Linux.