Skip to content

Commit 413d955

Browse files
authored
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
1 parent a16387a commit 413d955

File tree

5 files changed

+12
-13
lines changed

5 files changed

+12
-13
lines changed

Doc/library/shutil.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,8 +420,7 @@ the use of userspace buffers in Python as in "``outfd.write(infd.read())``".
420420

421421
On macOS `fcopyfile`_ is used to copy the file content (not metadata).
422422

423-
On Linux, Solaris and other POSIX platforms where :func:`os.sendfile` supports
424-
copies between 2 regular file descriptors :func:`os.sendfile` is used.
423+
On Linux :func:`os.sendfile` is used.
425424

426425
On Windows :func:`shutil.copyfile` uses a bigger default buffer size (1 MiB
427426
instead of 64 KiB) and a :func:`memoryview`-based variant of

Doc/whatsnew/3.8.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,7 @@ Optimizations
772772

773773
* :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`,
774774
:func:`shutil.copytree` and :func:`shutil.move` use platform-specific
775-
"fast-copy" syscalls on Linux, macOS and Solaris in order to copy the file
775+
"fast-copy" syscalls on Linux and macOS in order to copy the file
776776
more efficiently.
777777
"fast-copy" means that the copying operation occurs within the kernel,
778778
avoiding the use of userspace buffers in Python as in

Lib/shutil.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
import nt
5151

5252
COPY_BUFSIZE = 1024 * 1024 if _WINDOWS else 64 * 1024
53-
_HAS_SENDFILE = posix and hasattr(os, "sendfile")
53+
_USE_CP_SENDFILE = hasattr(os, "sendfile") and sys.platform.startswith("linux")
5454
_HAS_FCOPYFILE = posix and hasattr(posix, "_fcopyfile") # macOS
5555

5656
__all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2",
@@ -111,7 +111,7 @@ def _fastcopy_fcopyfile(fsrc, fdst, flags):
111111
def _fastcopy_sendfile(fsrc, fdst):
112112
"""Copy data from one regular mmap-like fd to another by using
113113
high-performance sendfile(2) syscall.
114-
This should work on Linux >= 2.6.33 and Solaris only.
114+
This should work on Linux >= 2.6.33 only.
115115
"""
116116
# Note: copyfileobj() is left alone in order to not introduce any
117117
# unexpected breakage. Possible risks by using zero-copy calls
@@ -122,7 +122,7 @@ def _fastcopy_sendfile(fsrc, fdst):
122122
# GzipFile (which decompresses data), HTTPResponse (which decodes
123123
# chunks).
124124
# - possibly others (e.g. encrypted fs/partition?)
125-
global _HAS_SENDFILE
125+
global _USE_CP_SENDFILE
126126
try:
127127
infd = fsrc.fileno()
128128
outfd = fdst.fileno()
@@ -152,7 +152,7 @@ def _fastcopy_sendfile(fsrc, fdst):
152152
# sendfile() on this platform (probably Linux < 2.6.33)
153153
# does not support copies between regular files (only
154154
# sockets).
155-
_HAS_SENDFILE = False
155+
_USE_CP_SENDFILE = False
156156
raise _GiveupOnFastCopy(err)
157157

158158
if err.errno == errno.ENOSPC: # filesystem is full
@@ -260,8 +260,8 @@ def copyfile(src, dst, *, follow_symlinks=True):
260260
return dst
261261
except _GiveupOnFastCopy:
262262
pass
263-
# Linux / Solaris
264-
elif _HAS_SENDFILE:
263+
# Linux
264+
elif _USE_CP_SENDFILE:
265265
try:
266266
_fastcopy_sendfile(fsrc, fdst)
267267
return dst

Lib/test/test_shutil.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2315,7 +2315,7 @@ def test_file2file_not_supported(self):
23152315
# Emulate a case where sendfile() only support file->socket
23162316
# fds. In such a case copyfile() is supposed to skip the
23172317
# fast-copy attempt from then on.
2318-
assert shutil._HAS_SENDFILE
2318+
assert shutil._USE_CP_SENDFILE
23192319
try:
23202320
with unittest.mock.patch(
23212321
self.PATCHPOINT,
@@ -2324,13 +2324,13 @@ def test_file2file_not_supported(self):
23242324
with self.assertRaises(_GiveupOnFastCopy):
23252325
shutil._fastcopy_sendfile(src, dst)
23262326
assert m.called
2327-
assert not shutil._HAS_SENDFILE
2327+
assert not shutil._USE_CP_SENDFILE
23282328

23292329
with unittest.mock.patch(self.PATCHPOINT) as m:
23302330
shutil.copyfile(TESTFN, TESTFN2)
23312331
assert not m.called
23322332
finally:
2333-
shutil._HAS_SENDFILE = True
2333+
shutil._USE_CP_SENDFILE = True
23342334

23352335

23362336
@unittest.skipIf(not MACOS, 'macOS only')

Misc/NEWS.d/3.8.0a1.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4450,7 +4450,7 @@ data_received() being called before connection_made().
44504450
44514451
:func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`,
44524452
:func:`shutil.copytree` and :func:`shutil.move` use platform-specific
4453-
fast-copy syscalls on Linux, Solaris and macOS in order to copy the file
4453+
fast-copy syscalls on Linux and macOS in order to copy the file
44544454
more efficiently. On Windows :func:`shutil.copyfile` uses a bigger default
44554455
buffer size (1 MiB instead of 16 KiB) and a :func:`memoryview`-based variant
44564456
of :func:`shutil.copyfileobj` is used. The speedup for copying a 512MiB file

0 commit comments

Comments
 (0)