diff --git a/hpath.cabal b/hpath.cabal index 843f98d..c07bc6a 100644 --- a/hpath.cabal +++ b/hpath.cabal @@ -35,6 +35,7 @@ library , deepseq , exceptions , hspec + , simple-sendfile >= 0.2.22 , unix >= 2.5 , unix-bytestring , utf8-string diff --git a/src/HPath/IO.hs b/src/HPath/IO.hs index 62c30bc..e4ed62d 100644 --- a/src/HPath/IO.hs +++ b/src/HPath/IO.hs @@ -107,6 +107,8 @@ import Data.Word import Foreign.C.Error ( eEXIST + , eINVAL + , eNOSYS , eNOTEMPTY , eXDEV ) @@ -136,6 +138,14 @@ import System.IO.Error catchIOError , ioeGetErrorType ) +import System.Linux.Sendfile + ( + sendfileFd + ) +import Network.Sendfile + ( + FileRange(..) + ) import System.Posix.ByteString ( exclusive @@ -341,7 +351,7 @@ recreateSymlink symsource newsym -- - `SameFile` if source and destination are the same file (`HPathIOException`) -- - `AlreadyExists` if destination already exists -- --- Note: calls `read`/`write` +-- Note: calls `sendfile` and possibly `read`/`write` as fallback copyFile :: Path Abs -- ^ source file -> Path Abs -- ^ destination file -> IO () @@ -369,7 +379,7 @@ copyFile from to = do -- - `InvalidArgument` if source file is wrong type (directory) -- - `SameFile` if source and destination are the same file (`HPathIOException`) -- --- Note: calls `read`/`write` +-- Note: calls `sendfile` and possibly `read`/`write` as fallback copyFileOverwrite :: Path Abs -- ^ source file -> Path Abs -- ^ destination file -> IO () @@ -397,9 +407,26 @@ _copyFile :: [SPDF.Flags] -> IO () _copyFile sflags dflags from to = - -- TODO: add sendfile support - void $ readWriteCopy (fromAbs from) (fromAbs to) + -- from sendfile(2) manpage: + -- Applications may wish to fall back to read(2)/write(2) in the case + -- where sendfile() fails with EINVAL or ENOSYS. + withAbsPath to $ \to' -> withAbsPath from $ \from' -> + catchErrno [eINVAL, eNOSYS] + (sendFileCopy from' to') + (void $ readWriteCopy from' to') where + -- this is low-level stuff utilizing sendfile(2) for speed + sendFileCopy source dest = + bracket (SPDT.openFd source SPI.ReadOnly sflags Nothing) + SPI.closeFd + $ \sfd -> do + fileM <- System.Posix.Files.ByteString.fileMode + <$> getFdStatus sfd + bracketeer (SPDT.openFd dest SPI.WriteOnly + dflags $ Just fileM) + SPI.closeFd + (\fd -> SPI.closeFd fd >> deleteFile to) + $ \dfd -> sendfileFd dfd sfd EntireFile (return ()) -- low-level copy operation utilizing read(2)/write(2) -- in case `sendFileCopy` fails/is unsupported readWriteCopy :: ByteString -> ByteString -> IO Int