Use streamly for copyFile

This commit is contained in:
Julian Ospald 2019-12-29 20:03:28 +01:00
parent 4ac3ee3e42
commit 200fc9b581
No known key found for this signature in database
GPG Key ID: 511B62C09D50CD28
2 changed files with 35 additions and 78 deletions

View File

@ -42,6 +42,7 @@ library
, exceptions , exceptions
, hspec , hspec
, simple-sendfile >= 0.2.24 , simple-sendfile >= 0.2.24
, streamly >= 0.7
, unix >= 2.5 , unix >= 2.5
, unix-bytestring , unix-bytestring
, utf8-string , utf8-string

View File

@ -150,9 +150,7 @@ import Data.Word
import Foreign.C.Error import Foreign.C.Error
( (
eEXIST eEXIST
, eINVAL
, eNOENT , eNOENT
, eNOSYS
, eNOTEMPTY , eNOTEMPTY
, eXDEV , eXDEV
, getErrno , getErrno
@ -177,16 +175,15 @@ import HPath
import HPath.Internal import HPath.Internal
import HPath.IO.Errors import HPath.IO.Errors
import Prelude hiding (appendFile, readFile, writeFile) import Prelude hiding (appendFile, readFile, writeFile)
import qualified Streamly.FileSystem.Handle as FH
import qualified Streamly.Internal.FileSystem.Handle as IFH
import qualified Streamly.Prelude as S
import qualified System.IO as SIO
import System.IO.Error import System.IO.Error
( (
catchIOError catchIOError
, ioeGetErrorType , ioeGetErrorType
) )
import System.Linux.Sendfile
(
sendfileFd
, FileRange(..)
)
import System.Posix.ByteString import System.Posix.ByteString
( (
exclusive exclusive
@ -466,6 +463,7 @@ recreateSymlink symsource@(MkPath symsourceBS) newsym@(MkPath newsymBS) cm
-- instead. -- instead.
-- --
-- In `Overwrite` copy mode only overwrites actual files, not directories. -- In `Overwrite` copy mode only overwrites actual files, not directories.
-- In `Strict` mode the destination file must not exist.
-- --
-- Safety/reliability concerns: -- Safety/reliability concerns:
-- --
@ -493,83 +491,41 @@ recreateSymlink symsource@(MkPath symsourceBS) newsym@(MkPath newsymBS) cm
-- --
-- Notes: -- Notes:
-- --
-- - calls `sendfile` and possibly `read`/`write` as fallback
-- - may call `getcwd` in Overwrite mode (if destination is a relative path) -- - may call `getcwd` in Overwrite mode (if destination is a relative path)
copyFile :: Path b1 -- ^ source file copyFile :: Path b1 -- ^ source file
-> Path b2 -- ^ destination file -> Path b2 -- ^ destination file
-> CopyMode -> CopyMode
-> IO () -> IO ()
copyFile from to cm = do copyFile fp@(MkPath from) tp@(MkPath to) cm = do
throwSameFile from to throwSameFile fp tp
bracket (do
case cm of fd <- openFd from SPI.ReadOnly [SPDF.oNofollow] Nothing
Strict -> _copyFile [SPDF.oNofollow] handle <- SPI.fdToHandle fd
[SPDF.oNofollow, SPDF.oExcl] pure (fd, handle))
from to (\(_, handle) -> SIO.hClose handle)
Overwrite -> $ \(fromFd, fH) -> do
catchIOError (_copyFile [SPDF.oNofollow] sourceFileMode <- System.Posix.Files.ByteString.fileMode <$> getFdStatus fromFd
[SPDF.oNofollow, SPDF.oTrunc] let dflags = [SPDF.oNofollow, case cm of
from to) $ \e -> Strict -> SPDF.oExcl
case ioeGetErrorType e of Overwrite -> SPDF.oTrunc]
-- if the destination file is not writable, we need to bracketeer (do
-- figure out if we can still copy by deleting it first fd <- openFd to SPI.WriteOnly dflags $ Just sourceFileMode
PermissionDenied -> do handle <- SPI.fdToHandle fd
exists <- doesFileExist to pure (fd, handle))
writable <- toAbs to >>= isWritable (\(_, handle) -> SIO.hClose handle)
if (exists && writable) (\(_, handle) -> do
then deleteFile to >> copyFile from to Strict SIO.hClose handle
else ioError e case cm of
_ -> ioError e -- if we created the file and copying failed, it's
-- safe to clean up
Strict -> deleteFile tp
_copyFile :: [SPDF.Flags] Overwrite -> pure ())
-> [SPDF.Flags] $ \(_, tH) -> do
-> Path b1 -- ^ source file SIO.hSetBinaryMode fH True
-> Path b2 -- ^ destination file SIO.hSetBinaryMode tH True
-> IO () streamlyCopy (fH, tH)
_copyFile sflags dflags (MkPath fromBS) to@(MkPath toBS)
=
-- 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.
catchErrno [eINVAL, eNOSYS]
(sendFileCopy fromBS toBS)
(void $ readWriteCopy fromBS toBS)
where where
copyWith copyAction source dest = streamlyCopy (fH, tH) = S.fold (FH.writeChunks tH) $ IFH.toChunksWithBufferOf (256*1024) fH
bracket (openFd source SPI.ReadOnly sflags Nothing)
SPI.closeFd
$ \sfd -> do
fileM <- System.Posix.Files.ByteString.fileMode
<$> getFdStatus sfd
bracketeer (openFd dest SPI.WriteOnly
dflags $ Just fileM)
SPI.closeFd
(\fd -> SPI.closeFd fd >> deleteFile to)
$ \dfd -> copyAction sfd dfd
-- this is low-level stuff utilizing sendfile(2) for speed
sendFileCopy :: ByteString -> ByteString -> IO ()
sendFileCopy = copyWith
(\sfd 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
readWriteCopy = copyWith
(\sfd dfd -> allocaBytes (fromIntegral bufSize)
$ \buf -> write' sfd dfd buf 0)
where
bufSize :: CSize
bufSize = 8192
write' :: Fd -> Fd -> Ptr Word8 -> Int -> IO Int
write' sfd dfd buf totalsize = do
size <- SPB.fdReadBuf sfd buf bufSize
if size == 0
then return $ fromIntegral totalsize
else do rsize <- SPB.fdWriteBuf dfd buf size
when (rsize /= size) (ioError $ userError
"wrong size!")
write' sfd dfd buf (totalsize + fromIntegral size)
-- |Copies a regular file, directory or symbolic link. In case of a -- |Copies a regular file, directory or symbolic link. In case of a
-- symbolic link it is just recreated, even if it points to a directory. -- symbolic link it is just recreated, even if it points to a directory.