diff -r fb70ea8b7b2d Modules/posixmodule.c
--- a/Modules/posixmodule.c Tue Apr 26 09:31:11 2016 +0300
+++ b/Modules/posixmodule.c Wed Apr 27 11:13:30 2016 +0200
@@ -87,6 +87,11 @@
#include
#endif
+#ifdef HAVE_COPY_FILE_RANGE
+#include
+#include
+#endif
+
#ifdef HAVE_SCHED_H
#include
#endif
@@ -8394,6 +8399,72 @@
}
#endif /* HAVE_SENDFILE */
+#ifdef HAVE_COPY_FILE_RANGE
+/* from copy_file_range()'s man page: */
+static loff_t
+copy_file_range(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out,
+ size_t len, unsigned int flags)
+{
+ return syscall(__NR_copy_file_range, fd_in, off_in, fd_out, off_out,
+ len, flags);
+}
+
+/* TODO: use clinic? */
+PyDoc_STRVAR(posix_copy_file_range__doc__,
+"copy_file_range(in, offset_in, out, offset_out, count, flags=0) -> \
+bytes_written\n\
+Copy count bytes from file descriptor in, starting from offset offset_in, \
+to file descriptor out, starting from offset offset_out. \
+If offset_in is None, then in is read from the current position; \
+respectively for offset_out.");
+
+static PyObject *
+posix_copy_file_range(PyObject *self, PyObject *args, PyObject *kwdict)
+{
+ int in, out;
+ off_t offset_in, *p_offset_in, offset_out, *p_offset_out, count;
+ Py_ssize_t ret;
+ int async_err = 0;
+ int flags = 0;
+ PyObject *offset_in_obj, *offset_out_obj;
+ static char *keywords[] = {"in", "offset_in", "out", "offset_out",
+ "count", "flags", NULL};
+
+ /* O types are used to handle offset_*_obj==None -> from current position */
+ if (!PyArg_ParseTupleAndKeywords(args, kwdict, "iOiOn|i:copy_file_range",
+ keywords, &in, &offset_in_obj, &out, &offset_out_obj, &count, &flags))
+ return NULL;
+
+ /* offset handling is complex. we convert the argument to a Python object.
+ * if None, then the corresponding pointer is NULL.
+ * otherwise, try to convert; if successful, use that
+ * and point the pointer there.
+ */
+ if (offset_in_obj == Py_None)
+ p_offset_in = NULL;
+ else {
+ if (!Py_off_t_converter(offset_in_obj, &offset_in))
+ return NULL;
+ p_offset_in = &offset_in;
+ }
+ if (offset_out_obj == Py_None)
+ p_offset_out = NULL;
+ else {
+ if (!Py_off_t_converter(offset_out_obj, &offset_out))
+ return NULL;
+ p_offset_out = &offset_out;
+ }
+
+ do {
+ Py_BEGIN_ALLOW_THREADS
+ ret = copy_file_range(in, p_offset_in, out, p_offset_out, count, flags);
+ Py_END_ALLOW_THREADS
+ } while (ret < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
+ if (ret < 0)
+ return async_err ? NULL : posix_error();
+ return Py_BuildValue("n", ret);
+}
+#endif /* HAVE_COPY_FILE_RANGE */
/*[clinic input]
os.fstat
@@ -12407,6 +12478,10 @@
{"sendfile", (PyCFunction)posix_sendfile, METH_VARARGS | METH_KEYWORDS,
posix_sendfile__doc__},
#endif
+#ifdef HAVE_COPY_FILE_RANGE
+ {"copy_file_range", (PyCFunction)posix_copy_file_range, METH_VARARGS | METH_KEYWORDS,
+ posix_copy_file_range__doc__},
+#endif
OS_FSTAT_METHODDEF
OS_ISATTY_METHODDEF
OS_PIPE_METHODDEF
diff -r fb70ea8b7b2d Lib/shutil.py
--- a/Lib/shutil.py Tue Apr 26 09:31:11 2016 +0300
+++ b/Lib/shutil.py Tue Apr 26 19:33:08 2016 +0200
@@ -69,11 +69,30 @@
def copyfileobj(fsrc, fdst, length=16*1024):
"""copy data from file-like object fsrc to file-like object fdst"""
- while 1:
- buf = fsrc.read(length)
- if not buf:
- break
- fdst.write(buf)
+ def brute_force_copy(fsrc, fdst, buffer_len):
+ while 1:
+ buf = fsrc.read(buffer_len)
+ if not buf:
+ break
+ fdst.write(buf)
+
+ if hasattr(os, 'copy_file_range'):
+ try:
+ while os.copy_file_range(fsrc.fileno(), None, fdst.fileno(), None,
+ length):
+ pass
+ except OSError as e:
+ if e.errno in (errno.EXDEV, errno.EBADFD):
+ # EBADFD can be in case fdst is open for append
+ # so try again with the old method too
+ brute_force_copy (fsrc, fdst, length)
+ elif e.errno == errno.EINVAL:
+ # in this case we ran out of src, we're done
+ return
+ else:
+ raise
+ else:
+ brute_force_copy (fsrc, fdst, length)
def _samefile(src, dst):
# Macintosh, Unix.
diff -r fb70ea8b7b2d Doc/library/os.rst
--- a/Doc/library/os.rst Tue Apr 26 09:31:11 2016 +0300
+++ b/Doc/library/os.rst Wed Apr 27 11:13:30 2016 +0200
@@ -671,6 +671,27 @@
pass
+.. function:: copy_file_range(in, offset_in, out, offset_out, count, flags=0)
+
+ Copy *count* bytes from file descriptor *in*, starting from offset
+ *offset_in*, to file descriptor *out*, starting from offset *offset_out*.
+ If *offset_in* is None, then *in* is read from the current position;
+ respectively for *offset_out*. *in* and *out* must reside in the same
+ filesystem, otherwise a *OSError* is raised with *errno* set to *errno.EXDEV*.
+
+ This copy is done without using any user space buffers, and some
+ filesystems could implement optimizations. Because of this, errors
+ like UnicodeDecodingError will not arise on files opened in text mode
+ with the wrong encoding.
+
+ The flags argument is provided to allow for future extensions and
+ currently must be set to 0.
+
+ Availability: some recent flavors of Unix (see the man page
+ :manpage:`copy_file_range(2)` for further information).
+
+ .. versionadded:: 3.6
+
.. function:: device_encoding(fd)
Return a string describing the encoding of the device associated with *fd*