{-# OPTIONS_GHC -optc-D_FILE_OFFSET_BITS=64 #-}
{-# LINE 1 "Network/Sendfile/Linux.hsc" #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE CPP #-}
module Network.Sendfile.Linux (
    sendfile
  , sendfile'
  , sendfileFd
  , sendfileFd'
  , sendfileWithHeader
  , sendfileFdWithHeader
  ) where
import Control.Exception
import Control.Monad
import Data.ByteString as B
import Data.ByteString.Internal
import Foreign.C.Error (eAGAIN, getErrno, throwErrno)
import Foreign.C.Types
import Foreign.Marshal (alloca)
import Foreign.Ptr (Ptr, plusPtr, castPtr)
import Foreign.ForeignPtr
import Foreign.Storable (poke, sizeOf)
import GHC.Conc (threadWaitWrite)
import Network.Sendfile.Types
import Network.Socket
import System.Posix.Files
import System.Posix.IO ( OpenMode(..)
                       , OpenFileFlags(..)
                       , defaultFileFlags
                       , closeFd
                       )
import System.Posix.Types
isLargeSize :: Bool
isLargeSize :: Bool
isLargeSize = CSize -> Int
forall a. Storable a => a -> Int
sizeOf (CSize
0 :: CSize) Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
8
safeSize :: CSize
safeSize :: CSize
safeSize
  | Bool
isLargeSize = CSize
2CSize -> Int -> CSize
forall a b. (Num a, Integral b) => a -> b -> a
^(Int
60 :: Int)
  | Bool
otherwise   = CSize
2CSize -> Int -> CSize
forall a b. (Num a, Integral b) => a -> b -> a
^(Int
30 :: Int)
sendfile :: Socket -> FilePath -> FileRange -> IO () -> IO ()
sendfile :: Socket -> FilePath -> FileRange -> IO () -> IO ()
sendfile Socket
sock FilePath
path FileRange
range IO ()
hook = IO Fd -> (Fd -> IO ()) -> (Fd -> IO ()) -> IO ()
forall a b c. IO a -> (a -> IO b) -> (a -> IO c) -> IO c
bracket IO Fd
setup Fd -> IO ()
teardown ((Fd -> IO ()) -> IO ()) -> (Fd -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \Fd
fd ->
    Socket -> Fd -> FileRange -> IO () -> IO ()
sendfileFd Socket
sock Fd
fd FileRange
range IO ()
hook
  where
    setup :: IO Fd
setup = FilePath -> OpenMode -> OpenFileFlags -> IO Fd
openFd FilePath
path OpenMode
ReadOnly OpenFileFlags
defaultFileFlags{nonBlock=True}
    teardown :: Fd -> IO ()
teardown = Fd -> IO ()
closeFd
sendfile' :: Fd -> ByteString -> FileRange -> IO () -> IO ()
sendfile' :: Fd -> ByteString -> FileRange -> IO () -> IO ()
sendfile' Fd
dst ByteString
path FileRange
range IO ()
hook = IO Fd -> (Fd -> IO ()) -> (Fd -> IO ()) -> IO ()
forall a b c. IO a -> (a -> IO b) -> (a -> IO c) -> IO c
bracket IO Fd
setup Fd -> IO ()
teardown ((Fd -> IO ()) -> IO ()) -> (Fd -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \Fd
src ->
    Fd -> Fd -> FileRange -> IO () -> IO ()
sendfileFd' Fd
dst Fd
src FileRange
range IO ()
hook
  where
    setup :: IO Fd
setup = ByteString -> OpenMode -> OpenFileFlags -> IO Fd
openFdBS ByteString
path OpenMode
ReadOnly OpenFileFlags
defaultFileFlags{nonBlock=True}
    teardown :: Fd -> IO ()
teardown = Fd -> IO ()
closeFd
sendfileFd :: Socket -> Fd -> FileRange -> IO () -> IO ()
sendfileFd :: Socket -> Fd -> FileRange -> IO () -> IO ()
sendfileFd Socket
sock Fd
fd FileRange
range IO ()
hook = do
{-# LINE 97 "Network/Sendfile/Linux.hsc" #-}
  withFdSocket sock $ \s -> do
    let dst = Fd s
{-# LINE 104 "Network/Sendfile/Linux.hsc" #-}
    sendfileFd' dst fd range hook
sendfileFd' :: Fd -> Fd -> FileRange -> IO () -> IO ()
sendfileFd' :: Fd -> Fd -> FileRange -> IO () -> IO ()
sendfileFd' Fd
dst Fd
src FileRange
range IO ()
hook =
    (Ptr COff -> IO ()) -> IO ()
forall a b. Storable a => (Ptr a -> IO b) -> IO b
alloca ((Ptr COff -> IO ()) -> IO ()) -> (Ptr COff -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \Ptr COff
offp -> case FileRange
range of
        FileRange
EntireFile -> do
            Ptr COff -> COff -> IO ()
forall a. Storable a => Ptr a -> a -> IO ()
poke Ptr COff
offp COff
0
            
            COff
len <- FileStatus -> COff
fileSize (FileStatus -> COff) -> IO FileStatus -> IO COff
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Fd -> IO FileStatus
getFdStatus Fd
src
            let len' :: CSize
len' = COff -> CSize
forall a b. (Integral a, Num b) => a -> b
fromIntegral COff
len
            Fd -> Fd -> Ptr COff -> CSize -> IO () -> IO ()
sendfileloop Fd
dst Fd
src Ptr COff
offp CSize
len' IO ()
hook
        PartOfFile Integer
off Integer
len -> do
            Ptr COff -> COff -> IO ()
forall a. Storable a => Ptr a -> a -> IO ()
poke Ptr COff
offp (Integer -> COff
forall a b. (Integral a, Num b) => a -> b
fromIntegral Integer
off)
            let len' :: CSize
len' = Integer -> CSize
forall a b. (Integral a, Num b) => a -> b
fromIntegral Integer
len
            Fd -> Fd -> Ptr COff -> CSize -> IO () -> IO ()
sendfileloop Fd
dst Fd
src Ptr COff
offp CSize
len' IO ()
hook
sendfileloop :: Fd -> Fd -> Ptr COff -> CSize -> IO () -> IO ()
sendfileloop :: Fd -> Fd -> Ptr COff -> CSize -> IO () -> IO ()
sendfileloop Fd
dst Fd
src Ptr COff
offp CSize
len IO ()
hook = do
    
    
    let toSend :: CSize
toSend
          | CSize
len CSize -> CSize -> Bool
forall a. Ord a => a -> a -> Bool
> CSize
safeSize = CSize
safeSize
          | Bool
otherwise      = CSize
len
    CSsize
bytes <- Fd -> Fd -> Ptr COff -> CSize -> IO CSsize
c_sendfile Fd
dst Fd
src Ptr COff
offp CSize
toSend
    case CSsize
bytes of
        -1 -> do
            Errno
errno <- IO Errno
getErrno
            if Errno
errno Errno -> Errno -> Bool
forall a. Eq a => a -> a -> Bool
== Errno
eAGAIN then do
                Fd -> IO ()
threadWaitWrite Fd
dst
                Fd -> Fd -> Ptr COff -> CSize -> IO () -> IO ()
sendfileloop Fd
dst Fd
src Ptr COff
offp CSize
len IO ()
hook
              else
                FilePath -> IO ()
forall a. FilePath -> IO a
throwErrno FilePath
"Network.SendFile.Linux.sendfileloop"
        CSsize
0  -> () -> IO ()
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return () 
        CSsize
_  -> do
            IO ()
hook
            let left :: CSize
left = CSize
len CSize -> CSize -> CSize
forall a. Num a => a -> a -> a
- CSsize -> CSize
forall a b. (Integral a, Num b) => a -> b
fromIntegral CSsize
bytes
            Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (CSize
left CSize -> CSize -> Bool
forall a. Eq a => a -> a -> Bool
/= CSize
0) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ Fd -> Fd -> Ptr COff -> CSize -> IO () -> IO ()
sendfileloop Fd
dst Fd
src Ptr COff
offp CSize
left IO ()
hook
foreign import ccall unsafe "sendfile"
    c_sendfile :: Fd -> Fd -> Ptr COff -> CSize -> IO CSsize
sendfileWithHeader :: Socket -> FilePath -> FileRange -> IO () -> [ByteString] -> IO ()
 Socket
sock FilePath
path FileRange
range IO ()
hook [ByteString]
hdr = do
    
    Socket -> ByteString -> IO ()
sendMsgMore Socket
sock (ByteString -> IO ()) -> ByteString -> IO ()
forall a b. (a -> b) -> a -> b
$ [ByteString] -> ByteString
B.concat [ByteString]
hdr
    Socket -> FilePath -> FileRange -> IO () -> IO ()
sendfile Socket
sock FilePath
path FileRange
range IO ()
hook
sendfileFdWithHeader :: Socket -> Fd -> FileRange -> IO () -> [ByteString] -> IO ()
 Socket
sock Fd
fd FileRange
range IO ()
hook [ByteString]
hdr = do
    
    Socket -> ByteString -> IO ()
sendMsgMore Socket
sock (ByteString -> IO ()) -> ByteString -> IO ()
forall a b. (a -> b) -> a -> b
$ [ByteString] -> ByteString
B.concat [ByteString]
hdr
    Socket -> Fd -> FileRange -> IO () -> IO ()
sendfileFd Socket
sock Fd
fd FileRange
range IO ()
hook
sendMsgMore :: Socket -> ByteString -> IO ()
sendMsgMore :: Socket -> ByteString -> IO ()
sendMsgMore Socket
sock ByteString
bs = ForeignPtr Word8 -> (Ptr Word8 -> IO ()) -> IO ()
forall a b. ForeignPtr a -> (Ptr a -> IO b) -> IO b
withForeignPtr ForeignPtr Word8
fptr ((Ptr Word8 -> IO ()) -> IO ()) -> (Ptr Word8 -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \Ptr Word8
ptr -> do
{-# LINE 203 "Network/Sendfile/Linux.hsc" #-}
  withFdSocket sock $ \fd -> do
    let s = Fd fd
{-# LINE 210 "Network/Sendfile/Linux.hsc" #-}
    let buf = castPtr (ptr `plusPtr` off)
        siz = fromIntegral len
    sendloop s buf siz
  where
    PS ForeignPtr Word8
fptr Int
off Int
len = ByteString
bs
sendloop :: Fd -> Ptr CChar -> CSize -> IO ()
sendloop :: Fd -> Ptr CChar -> CSize -> IO ()
sendloop Fd
s Ptr CChar
buf CSize
len = do
    CSsize
bytes <- Fd -> Ptr CChar -> CSize -> CInt -> IO CSsize
c_send Fd
s Ptr CChar
buf CSize
len (CInt
32768)
{-# LINE 219 "Network/Sendfile/Linux.hsc" #-}
    if bytes == -1 then do
        errno <- getErrno
        if errno == eAGAIN then do
            threadWaitWrite s
            sendloop s buf len
          else
            throwErrno "Network.SendFile.Linux.sendloop"
      else do
        let sent = fromIntegral bytes
        when (sent /= len) $ do
            let left = len - sent
                ptr = buf `plusPtr` fromIntegral bytes
            sendloop s ptr left
foreign import ccall unsafe "send"
  c_send :: Fd -> Ptr CChar -> CSize -> CInt -> IO CSsize