Commit 4a172ccc authored by Giampaolo Rodola's avatar Giampaolo Rodola Committed by GitHub

bpo-33671: efficient zero-copy for shutil.copy* functions (Linux, OSX and Win) (#7160)

* have shutil.copyfileobj use sendfile() if possible

* refactoring: use ctx manager

* add test with non-regular file obj

* emulate case where file size can't be determined

* reference _copyfileobj_sendfile directly

* add test for offset() at certain position

* add test for empty file

* add test for non regular file dst

* small refactoring

* leave copyfileobj() alone in order to not introduce any incompatibility

* minor refactoring

* remove old test

* update docstring

* update docstring; rename exception class

* detect platforms which only support file to socket zero copy

* don't run test on platforms where file-to-file zero copy is not supported

* use tempfiles

* reset verbosity

* add test for smaller chunks

* add big file size test

* add comment

* update doc

* update whatsnew doc

* update doc

* catch Exception

* remove unused import

* add test case for error on second sendfile() call

* turn docstring into comment

* add one more test

* update comment

* add Misc/NEWS entry

* get rid of COPY_BUFSIZE; it belongs to another PR

* update doc

* expose posix._fcopyfile() for OSX

* merge from linux branch

* merge from linux branch

* expose fcopyfile

* arg clinic for the win implementation

* convert path type to path_t

* expose CopyFileW

* fix windows tests

* release GIL

* minor refactoring

* update doc

* update comment

* update docstrings

* rename functions

* rename test classes

* update doc

* update doc

* update docstrings and comments

* avoid do import nt|posix modules if unnecessary

* set nt|posix modules to None if not available

* micro speedup

* update description

* add doc note

* use better wording in doc

* rename function using 'fastcopy' prefix instead of 'zerocopy'

* use :ref: in rst doc

* change wording in doc

* add test to make sure sendfile() doesn't get called aymore in case it doesn't support file to file copies

* move CopyFileW in _winapi and actually expose CopyFileExW instead

* fix line endings

* add tests for mode bits

* add docstring

* remove test file mode class; let's keep it for later when Istart addressing OSX fcopyfile() specific copies

* update doc to reflect new changes

* update doc

* adjust tests on win

* fix argument clinic error

* update doc

* OSX: expose copyfile(3) instead of fcopyfile(3); also expose flags arg to python

* osx / copyfile: use path_t instead of char

* do not set dst name in the OSError exception in order to remain consistent with platforms which cannot do that (e.g. linux)

* add same file test

* add test for same file

* have osx copyfile() pre-emptively check if src and dst are the same, otherwise it will return immedialtey and src file content gets deleted

* turn PermissionError into appropriate SameFileError

* expose ERROR_SHARING_VIOLATION in order to raise more appropriate SameFileError

* honour follow_symlinks arg when using CopyFileEx

* update Misc/NEWS

* expose CreateDirectoryEx mock

* change C type

* CreateDirectoryExW actual implementation

* provide specific makedirs() implementation for win

* fix typo

* skeleton for SetNamedSecurityInfo

* get security info for src path

* finally set security attrs

* add unit tests

* mimick os.makedirs() behavior and raise if dst dir exists

* set 2 paths for OSError object

* set 2 paths for OSError object

* expand windows test

* in case of exception on os.sendfile() set filename and filename2 exception attributes

* set 2 filenames (src, dst) for OSError in case copyfile() fails on OSX

* update doc

* do not use CreateDirectoryEx() in copytree() if source dir is a symlink (breaks test_copytree_symlink_dir); instead just create a plain dir and remain consistent with POSIX implementation

* use bytearray() and readinto()

* use memoryview() with bytearray()

* refactoring + introduce a new _fastcopy_binfileobj() fun

* remove CopyFileEx and other C wrappers

* remove code related to CopyFileEx

* Recognize binary files in copyfileobj()
...and use fastest _fastcopy_binfileobj() when possible

* set 1MB copy bufsize on win; also add a global _COPY_BUFSIZE variable

* use ctx manager for memoryview()

* update doc

* remove outdated doc

* remove last CopyFileEx remnants

* OSX - use fcopyfile(3) instead of copyfile(3)

...as an extra safety measure: in case src/dst are "exotic" files (non
regular or living on a network fs etc.) we better fail on open() instead
of copyfile(3) as we're not quite sure what's gonna happen in that
case.

* update doc
parent 33cd058f
...@@ -51,7 +51,9 @@ Directory and files operations ...@@ -51,7 +51,9 @@ Directory and files operations
.. function:: copyfile(src, dst, *, follow_symlinks=True) .. function:: copyfile(src, dst, *, follow_symlinks=True)
Copy the contents (no metadata) of the file named *src* to a file named Copy the contents (no metadata) of the file named *src* to a file named
*dst* and return *dst*. *src* and *dst* are path names given as strings. *dst* and return *dst* in the most efficient way possible.
*src* and *dst* are path names given as strings.
*dst* must be the complete target file name; look at :func:`shutil.copy` *dst* must be the complete target file name; look at :func:`shutil.copy`
for a copy that accepts a target directory path. If *src* and *dst* for a copy that accepts a target directory path. If *src* and *dst*
specify the same file, :exc:`SameFileError` is raised. specify the same file, :exc:`SameFileError` is raised.
...@@ -74,6 +76,10 @@ Directory and files operations ...@@ -74,6 +76,10 @@ Directory and files operations
Raise :exc:`SameFileError` instead of :exc:`Error`. Since the former is Raise :exc:`SameFileError` instead of :exc:`Error`. Since the former is
a subclass of the latter, this change is backward compatible. a subclass of the latter, this change is backward compatible.
.. versionchanged:: 3.8
Platform-specific fast-copy syscalls may be used internally in order to
copy the file more efficiently. See
:ref:`shutil-platform-dependent-efficient-copy-operations` section.
.. exception:: SameFileError .. exception:: SameFileError
...@@ -163,6 +169,11 @@ Directory and files operations ...@@ -163,6 +169,11 @@ Directory and files operations
Added *follow_symlinks* argument. Added *follow_symlinks* argument.
Now returns path to the newly created file. Now returns path to the newly created file.
.. versionchanged:: 3.8
Platform-specific fast-copy syscalls may be used internally in order to
copy the file more efficiently. See
:ref:`shutil-platform-dependent-efficient-copy-operations` section.
.. function:: copy2(src, dst, *, follow_symlinks=True) .. function:: copy2(src, dst, *, follow_symlinks=True)
Identical to :func:`~shutil.copy` except that :func:`copy2` Identical to :func:`~shutil.copy` except that :func:`copy2`
...@@ -185,6 +196,11 @@ Directory and files operations ...@@ -185,6 +196,11 @@ Directory and files operations
file system attributes too (currently Linux only). file system attributes too (currently Linux only).
Now returns path to the newly created file. Now returns path to the newly created file.
.. versionchanged:: 3.8
Platform-specific fast-copy syscalls may be used internally in order to
copy the file more efficiently. See
:ref:`shutil-platform-dependent-efficient-copy-operations` section.
.. function:: ignore_patterns(\*patterns) .. function:: ignore_patterns(\*patterns)
This factory function creates a function that can be used as a callable for This factory function creates a function that can be used as a callable for
...@@ -241,6 +257,10 @@ Directory and files operations ...@@ -241,6 +257,10 @@ Directory and files operations
Added the *ignore_dangling_symlinks* argument to silent dangling symlinks Added the *ignore_dangling_symlinks* argument to silent dangling symlinks
errors when *symlinks* is false. errors when *symlinks* is false.
.. versionchanged:: 3.8
Platform-specific fast-copy syscalls may be used internally in order to
copy the file more efficiently. See
:ref:`shutil-platform-dependent-efficient-copy-operations` section.
.. function:: rmtree(path, ignore_errors=False, onerror=None) .. function:: rmtree(path, ignore_errors=False, onerror=None)
...@@ -314,6 +334,11 @@ Directory and files operations ...@@ -314,6 +334,11 @@ Directory and files operations
.. versionchanged:: 3.5 .. versionchanged:: 3.5
Added the *copy_function* keyword argument. Added the *copy_function* keyword argument.
.. versionchanged:: 3.8
Platform-specific fast-copy syscalls may be used internally in order to
copy the file more efficiently. See
:ref:`shutil-platform-dependent-efficient-copy-operations` section.
.. function:: disk_usage(path) .. function:: disk_usage(path)
Return disk usage statistics about the given path as a :term:`named tuple` Return disk usage statistics about the given path as a :term:`named tuple`
...@@ -370,6 +395,28 @@ Directory and files operations ...@@ -370,6 +395,28 @@ Directory and files operations
operation. For :func:`copytree`, the exception argument is a list of 3-tuples operation. For :func:`copytree`, the exception argument is a list of 3-tuples
(*srcname*, *dstname*, *exception*). (*srcname*, *dstname*, *exception*).
.. _shutil-platform-dependent-efficient-copy-operations:
Platform-dependent efficient copy operations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Starting from Python 3.8 all functions involving a file copy (:func:`copyfile`,
:func:`copy`, :func:`copy2`, :func:`copytree`, and :func:`move`) may use
platform-specific "fast-copy" syscalls in order to copy the file more
efficiently (see :issue:`33671`).
"fast-copy" means that the copying operation occurs within the kernel, avoiding
the use of userspace buffers in Python as in "``outfd.write(infd.read())``".
On OSX `fcopyfile`_ is used to copy the file content (not metadata).
On Linux, Solaris and other POSIX platforms where :func:`os.sendfile` supports
copies between 2 regular file descriptors :func:`os.sendfile` is used.
If the fast-copy operation fails and no data was written in the destination
file then shutil will silently fallback on using less efficient
:func:`copyfileobj` function internally.
.. versionchanged:: 3.8
.. _shutil-copytree-example: .. _shutil-copytree-example:
...@@ -654,6 +701,8 @@ Querying the size of the output terminal ...@@ -654,6 +701,8 @@ Querying the size of the output terminal
.. versionadded:: 3.3 .. versionadded:: 3.3
.. _`fcopyfile`:
http://www.manpagez.com/man/3/copyfile/
.. _`Other Environment Variables`: .. _`Other Environment Variables`:
http://pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html#tag_002_003 http://pubs.opengroup.org/onlinepubs/7908799/xbd/envvar.html#tag_002_003
...@@ -90,10 +90,27 @@ New Modules ...@@ -90,10 +90,27 @@ New Modules
Improved Modules Improved Modules
================ ================
Optimizations Optimizations
============= =============
* :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`,
:func:`shutil.copytree` and :func:`shutil.move` use platform-specific
"fast-copy" syscalls on Linux, OSX and Solaris in order to copy the file more
efficiently.
"fast-copy" means that the copying operation occurs within the kernel,
avoiding the use of userspace buffers in Python as in
"``outfd.write(infd.read())``".
All other platforms not using such technique will rely on a faster
:func:`shutil.copyfile` implementation using :func:`memoryview`,
:class:`bytearray` and
:meth:`BufferedIOBase.readinto() <io.BufferedIOBase.readinto>`.
Finally, :func:`shutil.copyfile` default buffer size on Windows was increased
from 16KB to 1MB.
The speedup for copying a 512MB file within the same partition is about +26%
on Linux, +50% on OSX and +38% on Windows. Also, much less CPU cycles are
consumed.
(Contributed by Giampaolo Rodola' in :issue:`25427`.)
* The default protocol in the :mod:`pickle` module is now Protocol 4, * The default protocol in the :mod:`pickle` module is now Protocol 4,
first introduced in Python 3.4. It offers better performance and smaller first introduced in Python 3.4. It offers better performance and smaller
size compared to Protocol 3 available since Python 3.0. size compared to Protocol 3 available since Python 3.0.
......
...@@ -10,6 +10,7 @@ import stat ...@@ -10,6 +10,7 @@ import stat
import fnmatch import fnmatch
import collections import collections
import errno import errno
import io
try: try:
import zlib import zlib
...@@ -42,6 +43,16 @@ try: ...@@ -42,6 +43,16 @@ try:
except ImportError: except ImportError:
getgrnam = None getgrnam = None
posix = nt = None
if os.name == 'posix':
import posix
elif os.name == 'nt':
import nt
COPY_BUFSIZE = 1024 * 1024 if os.name == 'nt' else 16 * 1024
_HAS_SENDFILE = posix and hasattr(os, "sendfile")
_HAS_FCOPYFILE = posix and hasattr(posix, "_fcopyfile") # OSX
__all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2", __all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2",
"copytree", "move", "rmtree", "Error", "SpecialFileError", "copytree", "move", "rmtree", "Error", "SpecialFileError",
"ExecError", "make_archive", "get_archive_formats", "ExecError", "make_archive", "get_archive_formats",
...@@ -72,14 +83,124 @@ class RegistryError(Exception): ...@@ -72,14 +83,124 @@ class RegistryError(Exception):
"""Raised when a registry operation with the archiving """Raised when a registry operation with the archiving
and unpacking registries fails""" and unpacking registries fails"""
class _GiveupOnFastCopy(Exception):
"""Raised as a signal to fallback on using raw read()/write()
file copy when fast-copy functions fail to do so.
"""
def _fastcopy_osx(fsrc, fdst, flags):
"""Copy a regular file content or metadata by using high-performance
fcopyfile(3) syscall (OSX).
"""
try:
infd = fsrc.fileno()
outfd = fdst.fileno()
except Exception as err:
raise _GiveupOnFastCopy(err) # not a regular file
try:
posix._fcopyfile(infd, outfd, flags)
except OSError as err:
err.filename = fsrc.name
err.filename2 = fdst.name
if err.errno in {errno.EINVAL, errno.ENOTSUP}:
raise _GiveupOnFastCopy(err)
else:
raise err from None
def _fastcopy_sendfile(fsrc, fdst):
"""Copy data from one regular mmap-like fd to another by using
high-performance sendfile(2) syscall.
This should work on Linux >= 2.6.33 and Solaris only.
"""
# Note: copyfileobj() is left alone in order to not introduce any
# unexpected breakage. Possible risks by using zero-copy calls
# in copyfileobj() are:
# - fdst cannot be open in "a"(ppend) mode
# - fsrc and fdst may be open in "t"(ext) mode
# - fsrc may be a BufferedReader (which hides unread data in a buffer),
# GzipFile (which decompresses data), HTTPResponse (which decodes
# chunks).
# - possibly others (e.g. encrypted fs/partition?)
global _HAS_SENDFILE
try:
infd = fsrc.fileno()
outfd = fdst.fileno()
except Exception as err:
raise _GiveupOnFastCopy(err) # not a regular file
# Hopefully the whole file will be copied in a single call.
# sendfile() is called in a loop 'till EOF is reached (0 return)
# so a bufsize smaller or bigger than the actual file size
# should not make any difference, also in case the file content
# changes while being copied.
try:
blocksize = max(os.fstat(infd).st_size, 2 ** 23) # min 8MB
except Exception:
blocksize = 2 ** 27 # 128MB
offset = 0
while True:
try:
sent = os.sendfile(outfd, infd, offset, blocksize)
except OSError as err:
# ...in oder to have a more informative exception.
err.filename = fsrc.name
err.filename2 = fdst.name
if err.errno == errno.ENOTSOCK:
# sendfile() on this platform (probably Linux < 2.6.33)
# does not support copies between regular files (only
# sockets).
_HAS_SENDFILE = False
raise _GiveupOnFastCopy(err)
if err.errno == errno.ENOSPC: # filesystem is full
raise err from None
# Give up on first call and if no data was copied.
if offset == 0 and os.lseek(outfd, 0, os.SEEK_CUR) == 0:
raise _GiveupOnFastCopy(err)
raise err
else:
if sent == 0:
break # EOF
offset += sent
def _copybinfileobj(fsrc, fdst, length=COPY_BUFSIZE):
"""Copy 2 regular file objects open in binary mode."""
# Localize variable access to minimize overhead.
fsrc_readinto = fsrc.readinto
fdst_write = fdst.write
with memoryview(bytearray(length)) as mv:
while True:
n = fsrc_readinto(mv)
if not n:
break
elif n < length:
fdst_write(mv[:n])
else:
fdst_write(mv)
def _is_binary_files_pair(fsrc, fdst):
return hasattr(fsrc, 'readinto') and \
isinstance(fsrc, io.BytesIO) or 'b' in getattr(fsrc, 'mode', '') and \
isinstance(fdst, io.BytesIO) or 'b' in getattr(fdst, 'mode', '')
def copyfileobj(fsrc, fdst, length=16*1024): def copyfileobj(fsrc, fdst, length=COPY_BUFSIZE):
"""copy data from file-like object fsrc to file-like object fdst""" """copy data from file-like object fsrc to file-like object fdst"""
while 1: if _is_binary_files_pair(fsrc, fdst):
buf = fsrc.read(length) _copybinfileobj(fsrc, fdst, length=length)
if not buf: else:
break # Localize variable access to minimize overhead.
fdst.write(buf) fsrc_read = fsrc.read
fdst_write = fdst.write
while 1:
buf = fsrc_read(length)
if not buf:
break
fdst_write(buf)
def _samefile(src, dst): def _samefile(src, dst):
# Macintosh, Unix. # Macintosh, Unix.
...@@ -117,9 +238,23 @@ def copyfile(src, dst, *, follow_symlinks=True): ...@@ -117,9 +238,23 @@ def copyfile(src, dst, *, follow_symlinks=True):
if not follow_symlinks and os.path.islink(src): if not follow_symlinks and os.path.islink(src):
os.symlink(os.readlink(src), dst) os.symlink(os.readlink(src), dst)
else: else:
with open(src, 'rb') as fsrc: with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
with open(dst, 'wb') as fdst: if _HAS_SENDFILE:
copyfileobj(fsrc, fdst) try:
_fastcopy_sendfile(fsrc, fdst)
return dst
except _GiveupOnFastCopy:
pass
if _HAS_FCOPYFILE:
try:
_fastcopy_osx(fsrc, fdst, posix._COPYFILE_DATA)
return dst
except _GiveupOnFastCopy:
pass
_copybinfileobj(fsrc, fdst)
return dst return dst
def copymode(src, dst, *, follow_symlinks=True): def copymode(src, dst, *, follow_symlinks=True):
...@@ -244,13 +379,12 @@ def copy(src, dst, *, follow_symlinks=True): ...@@ -244,13 +379,12 @@ def copy(src, dst, *, follow_symlinks=True):
def copy2(src, dst, *, follow_symlinks=True): def copy2(src, dst, *, follow_symlinks=True):
"""Copy data and all stat info ("cp -p src dst"). Return the file's """Copy data and all stat info ("cp -p src dst"). Return the file's
destination." destination.
The destination may be a directory. The destination may be a directory.
If follow_symlinks is false, symlinks won't be followed. This If follow_symlinks is false, symlinks won't be followed. This
resembles GNU's "cp -P src dst". resembles GNU's "cp -P src dst".
""" """
if os.path.isdir(dst): if os.path.isdir(dst):
dst = os.path.join(dst, os.path.basename(src)) dst = os.path.join(dst, os.path.basename(src))
...@@ -1015,7 +1149,6 @@ if hasattr(os, 'statvfs'): ...@@ -1015,7 +1149,6 @@ if hasattr(os, 'statvfs'):
elif os.name == 'nt': elif os.name == 'nt':
import nt
__all__.append('disk_usage') __all__.append('disk_usage')
_ntuple_diskusage = collections.namedtuple('usage', 'total used free') _ntuple_diskusage = collections.namedtuple('usage', 'total used free')
......
This diff is collapsed.
:func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`,
:func:`shutil.copytree` and :func:`shutil.move` use platform-specific
fast-copy syscalls on Linux, Solaris and OSX in order to copy the file
more efficiently. All other platforms not using such technique will rely on a
faster :func:`shutil.copyfile` implementation using :func:`memoryview`,
:class:`bytearray` and
:meth:`BufferedIOBase.readinto() <io.BufferedIOBase.readinto>`.
Finally, :func:`shutil.copyfile` default buffer size on Windows was increased
from 16KB to 1MB. The speedup for copying a 512MB file is about +26% on Linux,
+50% on OSX and +38% on Windows. Also, much less CPU cycles are consumed
(Contributed by Giampaolo Rodola' in :issue:`25427`.)
...@@ -163,6 +163,7 @@ create_converter('LPSECURITY_ATTRIBUTES', '" F_POINTER "') ...@@ -163,6 +163,7 @@ create_converter('LPSECURITY_ATTRIBUTES', '" F_POINTER "')
create_converter('BOOL', 'i') # F_BOOL used previously (always 'i') create_converter('BOOL', 'i') # F_BOOL used previously (always 'i')
create_converter('DWORD', 'k') # F_DWORD is always "k" (which is much shorter) create_converter('DWORD', 'k') # F_DWORD is always "k" (which is much shorter)
create_converter('LPCTSTR', 's') create_converter('LPCTSTR', 's')
create_converter('LPCWSTR', 'u')
create_converter('LPWSTR', 'u') create_converter('LPWSTR', 'u')
create_converter('UINT', 'I') # F_UINT used previously (always 'I') create_converter('UINT', 'I') # F_UINT used previously (always 'I')
...@@ -186,7 +187,7 @@ class DWORD_return_converter(CReturnConverter): ...@@ -186,7 +187,7 @@ class DWORD_return_converter(CReturnConverter):
data.return_conversion.append( data.return_conversion.append(
'return_value = Py_BuildValue("k", _return_value);\n') 'return_value = Py_BuildValue("k", _return_value);\n')
[python start generated code]*/ [python start generated code]*/
/*[python end generated code: output=da39a3ee5e6b4b0d input=4527052fe06e5823]*/ /*[python end generated code: output=da39a3ee5e6b4b0d input=27456f8555228b62]*/
#include "clinic/_winapi.c.h" #include "clinic/_winapi.c.h"
......
...@@ -3853,6 +3853,40 @@ exit: ...@@ -3853,6 +3853,40 @@ exit:
return return_value; return return_value;
} }
#if defined(__APPLE__)
PyDoc_STRVAR(os__fcopyfile__doc__,
"_fcopyfile($module, infd, outfd, flags, /)\n"
"--\n"
"\n"
"Efficiently copy content or metadata of 2 regular file descriptors (OSX).");
#define OS__FCOPYFILE_METHODDEF \
{"_fcopyfile", (PyCFunction)os__fcopyfile, METH_FASTCALL, os__fcopyfile__doc__},
static PyObject *
os__fcopyfile_impl(PyObject *module, int infd, int outfd, int flags);
static PyObject *
os__fcopyfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *return_value = NULL;
int infd;
int outfd;
int flags;
if (!_PyArg_ParseStack(args, nargs, "iii:_fcopyfile",
&infd, &outfd, &flags)) {
goto exit;
}
return_value = os__fcopyfile_impl(module, infd, outfd, flags);
exit:
return return_value;
}
#endif /* defined(__APPLE__) */
PyDoc_STRVAR(os_fstat__doc__, PyDoc_STRVAR(os_fstat__doc__,
"fstat($module, /, fd)\n" "fstat($module, /, fd)\n"
"--\n" "--\n"
...@@ -6414,6 +6448,10 @@ exit: ...@@ -6414,6 +6448,10 @@ exit:
#define OS_PREADV_METHODDEF #define OS_PREADV_METHODDEF
#endif /* !defined(OS_PREADV_METHODDEF) */ #endif /* !defined(OS_PREADV_METHODDEF) */
#ifndef OS__FCOPYFILE_METHODDEF
#define OS__FCOPYFILE_METHODDEF
#endif /* !defined(OS__FCOPYFILE_METHODDEF) */
#ifndef OS_PIPE_METHODDEF #ifndef OS_PIPE_METHODDEF
#define OS_PIPE_METHODDEF #define OS_PIPE_METHODDEF
#endif /* !defined(OS_PIPE_METHODDEF) */ #endif /* !defined(OS_PIPE_METHODDEF) */
...@@ -6589,4 +6627,4 @@ exit: ...@@ -6589,4 +6627,4 @@ exit:
#ifndef OS_GETRANDOM_METHODDEF #ifndef OS_GETRANDOM_METHODDEF
#define OS_GETRANDOM_METHODDEF #define OS_GETRANDOM_METHODDEF
#endif /* !defined(OS_GETRANDOM_METHODDEF) */ #endif /* !defined(OS_GETRANDOM_METHODDEF) */
/*[clinic end generated code: output=8d3d9dddf254c3c2 input=a9049054013a1b77]*/ /*[clinic end generated code: output=b5d1ec71bc6f0651 input=a9049054013a1b77]*/
...@@ -97,6 +97,10 @@ corresponding Unix manual entries for more information on calls."); ...@@ -97,6 +97,10 @@ corresponding Unix manual entries for more information on calls.");
#include <sys/sendfile.h> #include <sys/sendfile.h>
#endif #endif
#if defined(__APPLE__)
#include <copyfile.h>
#endif
#ifdef HAVE_SCHED_H #ifdef HAVE_SCHED_H
#include <sched.h> #include <sched.h>
#endif #endif
...@@ -8742,6 +8746,34 @@ done: ...@@ -8742,6 +8746,34 @@ done:
#endif /* HAVE_SENDFILE */ #endif /* HAVE_SENDFILE */
#if defined(__APPLE__)
/*[clinic input]
os._fcopyfile
infd: int
outfd: int
flags: int
/
Efficiently copy content or metadata of 2 regular file descriptors (OSX).
[clinic start generated code]*/
static PyObject *
os__fcopyfile_impl(PyObject *module, int infd, int outfd, int flags)
/*[clinic end generated code: output=8e8885c721ec38e3 input=aeb9456804eec879]*/
{
int ret;
Py_BEGIN_ALLOW_THREADS
ret = fcopyfile(infd, outfd, NULL, flags);
Py_END_ALLOW_THREADS
if (ret < 0)
return posix_error();
Py_RETURN_NONE;
}
#endif
/*[clinic input] /*[clinic input]
os.fstat os.fstat
...@@ -12918,6 +12950,7 @@ static PyMethodDef posix_methods[] = { ...@@ -12918,6 +12950,7 @@ static PyMethodDef posix_methods[] = {
OS_UTIME_METHODDEF OS_UTIME_METHODDEF
OS_TIMES_METHODDEF OS_TIMES_METHODDEF
OS__EXIT_METHODDEF OS__EXIT_METHODDEF
OS__FCOPYFILE_METHODDEF
OS_EXECV_METHODDEF OS_EXECV_METHODDEF
OS_EXECVE_METHODDEF OS_EXECVE_METHODDEF
OS_SPAWNV_METHODDEF OS_SPAWNV_METHODDEF
...@@ -13537,6 +13570,10 @@ all_ins(PyObject *m) ...@@ -13537,6 +13570,10 @@ all_ins(PyObject *m)
if (PyModule_AddIntMacro(m, GRND_NONBLOCK)) return -1; if (PyModule_AddIntMacro(m, GRND_NONBLOCK)) return -1;
#endif #endif
#if defined(__APPLE__)
if (PyModule_AddIntConstant(m, "_COPYFILE_DATA", COPYFILE_DATA)) return -1;
#endif
return 0; return 0;
} }
......
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