diff -r fb70ea8b7b2d Modules/posixmodule.c --- a/Modules/posixmodule.c Tue Apr 26 09:31:11 2016 +0300 +++ b/Modules/posixmodule.c Tue Apr 26 15:44:45 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,74 @@ } #endif /* HAVE_SENDFILE */ +#ifdef HAVE_COPY_FILE_RANGE +/* The name says posix but currently it's Linux only */ + +/* 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) ? posix_error() : NULL; + return Py_BuildValue("n", ret); +} +#endif /* HAVE_COPY_FILE_RANGE */ /*[clinic input] os.fstat @@ -12407,6 +12477,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 15:44:45 2016 +0200 @@ -69,11 +69,16 @@ 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) + if hasattr(os, 'copy_file_range'): + while os.copy_file_range(fsrc.fileno(), None, fdst.fileno(), None, length): + pass + else: + # normal brute force way + while 1: + buf = fsrc.read(length) + if not buf: + break + fdst.write(buf) 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 Tue Apr 26 16:03:27 2016 +0200 @@ -671,6 +671,21 @@ 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*. + This copy is done without using any user space buffers, and some + filesystems could implement optimizations. The flags argument is + provided to allow for future extensions and currently must be 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* diff -r fb70ea8b7b2d Misc/NEWS --- a/Misc/NEWS Tue Apr 26 09:31:11 2016 +0300 +++ b/Misc/NEWS Tue Apr 26 16:09:10 2016 +0200 @@ -874,6 +874,9 @@ - Issue #26406: Avoid unnecessary serialization of getaddrinfo(3) calls on current versions of OpenBSD and NetBSD. Patch by A. Jesse Jiryu Davis. +- Issue #26826: Expose new copy_file_range() syscal in os module and use it + to improve shutils.copy() + IDLE ---- diff -r fb70ea8b7b2d Misc/ACKS --- a/Misc/ACKS Tue Apr 26 09:31:11 2016 +0300 +++ b/Misc/ACKS Tue Apr 26 16:37:34 2016 +0200 @@ -351,6 +351,7 @@ Jack Diederich Daniel Diniz Humberto Diogenes +Marcos Dione Yves Dionne Daniel Dittmar Josip Djolonga