Commit 413d955f authored by Giampaolo Rodola's avatar Giampaolo Rodola Committed by GitHub

bpo-36610: shutil.copyfile(): use sendfile() on Linux only (GH-13675)

...and avoid using it on Solaris as it can raise EINVAL if offset is equal or bigger than the size of the file
parent a16387ab
...@@ -420,8 +420,7 @@ the use of userspace buffers in Python as in "``outfd.write(infd.read())``". ...@@ -420,8 +420,7 @@ the use of userspace buffers in Python as in "``outfd.write(infd.read())``".
On macOS `fcopyfile`_ is used to copy the file content (not metadata). On macOS `fcopyfile`_ is used to copy the file content (not metadata).
On Linux, Solaris and other POSIX platforms where :func:`os.sendfile` supports On Linux :func:`os.sendfile` is used.
copies between 2 regular file descriptors :func:`os.sendfile` is used.
On Windows :func:`shutil.copyfile` uses a bigger default buffer size (1 MiB On Windows :func:`shutil.copyfile` uses a bigger default buffer size (1 MiB
instead of 64 KiB) and a :func:`memoryview`-based variant of instead of 64 KiB) and a :func:`memoryview`-based variant of
......
...@@ -772,7 +772,7 @@ Optimizations ...@@ -772,7 +772,7 @@ Optimizations
* :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`, * :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`,
:func:`shutil.copytree` and :func:`shutil.move` use platform-specific :func:`shutil.copytree` and :func:`shutil.move` use platform-specific
"fast-copy" syscalls on Linux, macOS and Solaris in order to copy the file "fast-copy" syscalls on Linux and macOS in order to copy the file
more efficiently. more efficiently.
"fast-copy" means that the copying operation occurs within the kernel, "fast-copy" means that the copying operation occurs within the kernel,
avoiding the use of userspace buffers in Python as in avoiding the use of userspace buffers in Python as in
......
...@@ -50,7 +50,7 @@ elif _WINDOWS: ...@@ -50,7 +50,7 @@ elif _WINDOWS:
import nt import nt
COPY_BUFSIZE = 1024 * 1024 if _WINDOWS else 64 * 1024 COPY_BUFSIZE = 1024 * 1024 if _WINDOWS else 64 * 1024
_HAS_SENDFILE = posix and hasattr(os, "sendfile") _USE_CP_SENDFILE = hasattr(os, "sendfile") and sys.platform.startswith("linux")
_HAS_FCOPYFILE = posix and hasattr(posix, "_fcopyfile") # macOS _HAS_FCOPYFILE = posix and hasattr(posix, "_fcopyfile") # macOS
__all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2", __all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2",
...@@ -111,7 +111,7 @@ def _fastcopy_fcopyfile(fsrc, fdst, flags): ...@@ -111,7 +111,7 @@ def _fastcopy_fcopyfile(fsrc, fdst, flags):
def _fastcopy_sendfile(fsrc, fdst): def _fastcopy_sendfile(fsrc, fdst):
"""Copy data from one regular mmap-like fd to another by using """Copy data from one regular mmap-like fd to another by using
high-performance sendfile(2) syscall. high-performance sendfile(2) syscall.
This should work on Linux >= 2.6.33 and Solaris only. This should work on Linux >= 2.6.33 only.
""" """
# Note: copyfileobj() is left alone in order to not introduce any # Note: copyfileobj() is left alone in order to not introduce any
# unexpected breakage. Possible risks by using zero-copy calls # unexpected breakage. Possible risks by using zero-copy calls
...@@ -122,7 +122,7 @@ def _fastcopy_sendfile(fsrc, fdst): ...@@ -122,7 +122,7 @@ def _fastcopy_sendfile(fsrc, fdst):
# GzipFile (which decompresses data), HTTPResponse (which decodes # GzipFile (which decompresses data), HTTPResponse (which decodes
# chunks). # chunks).
# - possibly others (e.g. encrypted fs/partition?) # - possibly others (e.g. encrypted fs/partition?)
global _HAS_SENDFILE global _USE_CP_SENDFILE
try: try:
infd = fsrc.fileno() infd = fsrc.fileno()
outfd = fdst.fileno() outfd = fdst.fileno()
...@@ -152,7 +152,7 @@ def _fastcopy_sendfile(fsrc, fdst): ...@@ -152,7 +152,7 @@ def _fastcopy_sendfile(fsrc, fdst):
# sendfile() on this platform (probably Linux < 2.6.33) # sendfile() on this platform (probably Linux < 2.6.33)
# does not support copies between regular files (only # does not support copies between regular files (only
# sockets). # sockets).
_HAS_SENDFILE = False _USE_CP_SENDFILE = False
raise _GiveupOnFastCopy(err) raise _GiveupOnFastCopy(err)
if err.errno == errno.ENOSPC: # filesystem is full if err.errno == errno.ENOSPC: # filesystem is full
...@@ -260,8 +260,8 @@ def copyfile(src, dst, *, follow_symlinks=True): ...@@ -260,8 +260,8 @@ def copyfile(src, dst, *, follow_symlinks=True):
return dst return dst
except _GiveupOnFastCopy: except _GiveupOnFastCopy:
pass pass
# Linux / Solaris # Linux
elif _HAS_SENDFILE: elif _USE_CP_SENDFILE:
try: try:
_fastcopy_sendfile(fsrc, fdst) _fastcopy_sendfile(fsrc, fdst)
return dst return dst
......
...@@ -2315,7 +2315,7 @@ class TestZeroCopySendfile(_ZeroCopyFileTest, unittest.TestCase): ...@@ -2315,7 +2315,7 @@ class TestZeroCopySendfile(_ZeroCopyFileTest, unittest.TestCase):
# Emulate a case where sendfile() only support file->socket # Emulate a case where sendfile() only support file->socket
# fds. In such a case copyfile() is supposed to skip the # fds. In such a case copyfile() is supposed to skip the
# fast-copy attempt from then on. # fast-copy attempt from then on.
assert shutil._HAS_SENDFILE assert shutil._USE_CP_SENDFILE
try: try:
with unittest.mock.patch( with unittest.mock.patch(
self.PATCHPOINT, self.PATCHPOINT,
...@@ -2324,13 +2324,13 @@ class TestZeroCopySendfile(_ZeroCopyFileTest, unittest.TestCase): ...@@ -2324,13 +2324,13 @@ class TestZeroCopySendfile(_ZeroCopyFileTest, unittest.TestCase):
with self.assertRaises(_GiveupOnFastCopy): with self.assertRaises(_GiveupOnFastCopy):
shutil._fastcopy_sendfile(src, dst) shutil._fastcopy_sendfile(src, dst)
assert m.called assert m.called
assert not shutil._HAS_SENDFILE assert not shutil._USE_CP_SENDFILE
with unittest.mock.patch(self.PATCHPOINT) as m: with unittest.mock.patch(self.PATCHPOINT) as m:
shutil.copyfile(TESTFN, TESTFN2) shutil.copyfile(TESTFN, TESTFN2)
assert not m.called assert not m.called
finally: finally:
shutil._HAS_SENDFILE = True shutil._USE_CP_SENDFILE = True
@unittest.skipIf(not MACOS, 'macOS only') @unittest.skipIf(not MACOS, 'macOS only')
......
...@@ -4450,7 +4450,7 @@ data_received() being called before connection_made(). ...@@ -4450,7 +4450,7 @@ data_received() being called before connection_made().
:func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`, :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`,
:func:`shutil.copytree` and :func:`shutil.move` use platform-specific :func:`shutil.copytree` and :func:`shutil.move` use platform-specific
fast-copy syscalls on Linux, Solaris and macOS in order to copy the file fast-copy syscalls on Linux and macOS in order to copy the file
more efficiently. On Windows :func:`shutil.copyfile` uses a bigger default more efficiently. On Windows :func:`shutil.copyfile` uses a bigger default
buffer size (1 MiB instead of 16 KiB) and a :func:`memoryview`-based variant buffer size (1 MiB instead of 16 KiB) and a :func:`memoryview`-based variant
of :func:`shutil.copyfileobj` is used. The speedup for copying a 512MiB file of :func:`shutil.copyfileobj` is used. The speedup for copying a 512MiB file
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment