diff -r 2fcfa9badfcd Doc/library/os.rst --- a/Doc/library/os.rst Wed Jun 08 13:32:49 2016 +0000 +++ b/Doc/library/os.rst Thu Jun 09 13:49:59 2016 +0200 @@ -680,6 +680,31 @@ pass +.. function:: copy_file_range(src, dst, count, offset_src=None, offset_dst=None, flags=0) + + Copy *count* bytes from file descriptor *src*, starting from offset + *offset_src*, to file descriptor *dst*, starting from offset *offset_dst*. + If *offset_src* is None, then *src* is read from the current position; + respectively for *offset_dst*. The files pointed by *src* and *dst* + must reside in the same filesystem, otherwise a :exc:`OSError` is + raised with :attr:`~OSError.errno` set to :data:`errno.EXDEV`. + + This copy is done without using any user space buffers, and some + filesystems could implement optimizations. The copy is done as if + both files are opened as binary. + + The *flags* argument is provided for compatibility with the only currently + supported implementation (Linux >=4.5) to allow for future extensions, and + currently can only be 0. + + The return value is the amount of bytes copied. This could be less than the + amount requested. + + 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 2fcfa9badfcd Lib/test/test_os.py --- a/Lib/test/test_os.py Wed Jun 08 13:32:49 2016 +0000 +++ b/Lib/test/test_os.py Thu Jun 09 13:49:59 2016 +0200 @@ -244,6 +243,35 @@ except (NotImplementedError, OSError): pass # No OS support or unprivileged user + @unittest.skipUnless(hasattr(os, 'copy_file_range'), 'test needs os.copy_file_range()') + def test_copy_file_range_ok(self): + TESTFN2 = support.TESTFN + ".3" + data = b'0123456789' + + create_file(support.TESTFN, data) + self.addCleanup(support.unlink, support.TESTFN) + + in_file = open (support.TESTFN, 'rb') + self.addCleanup(in_file.close) + in_fd = in_file.fileno() + + out_file = open (TESTFN2, 'w+b') + self.addCleanup(support.unlink, TESTFN2) + self.addCleanup(out_file.close) + out_fd = out_file.fileno() + + try: + i = os.copy_file_range (in_fd, out_fd, 5); + except OSError as e: + if e.errno != errno.ENOSYS: + raise + self.skipTest(e) + else: + self.assertIn (i, range (0, 6)); + + in_file = open (TESTFN2, 'rb') + self.assertEqual (in_file.read(), data[:i]) + # Test attributes on return values from os.*stat* family. class StatAttributeTests(unittest.TestCase): @@ -1559,6 +1587,14 @@ self.check(os.get_blocking) self.check(os.set_blocking, True) + @unittest.skipUnless(hasattr(os, 'copy_file_range'), 'test needs os.copy_file_range()') + def test_copy_file_range_in(self): + try: + self.check(os.copy_file_range, 0, 0) + except OSError as e: + if e.errno != errno.ENOSYS: + raise + self.skipTest(e) class LinkTests(unittest.TestCase): def setUp(self): diff -r 2fcfa9badfcd Modules/posixmodule.c --- a/Modules/posixmodule.c Wed Jun 08 13:32:49 2016 +0000 +++ b/Modules/posixmodule.c Thu Jun 09 13:49:59 2016 +0200 @@ -95,6 +95,11 @@ #include #endif +#ifdef HAVE_COPY_FILE_RANGE +#include +#include +#endif + #ifdef HAVE_SCHED_H #include #endif @@ -8404,6 +8409,75 @@ } #endif /* HAVE_SENDFILE */ +#ifdef HAVE_COPY_FILE_RANGE +/* modified example found in copy_file_range()'s man page: */ +/* changed the return type to ssize_t so it can return -1 */ +static ssize_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); +} + +PyDoc_STRVAR(posix_copy_file_range__doc__, +"copy_file_range(src, dst, count, offset_src=None, offset_dst=None, flags=0) -> \ +bytes_written\n\ +Copy count bytes from file descriptor src, starting from offset offset_src, \ +to file descriptor dst, starting from offset offset_dst. \ +If offset_src is None, then src is read from the current position; \ +respectively for offset_dst."); + +static PyObject * +posix_copy_file_range(PyObject *self, PyObject *args, PyObject *kwdict) +{ + int src, dst; + off_t offset_src, *p_offset_src, offset_dst, *p_offset_dst; + Py_ssize_t count, ret; + int async_err = 0; + int flags = 0; + PyObject *offset_src_obj = Py_None, *offset_dst_obj = Py_None; + static char *keywords[] = {"src", "dst", "count", + "offset_src", "offset_dst", "flags", NULL}; + + /* O types are used to handle offset_*_obj==None -> from current position */ + if (!PyArg_ParseTupleAndKeywords(args, kwdict, "iin|OOi:copy_file_range", + keywords, &src, &dst, &count, &offset_src_obj, &offset_dst_obj, &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_src_obj == Py_None) + p_offset_src = NULL; + else { + if (!Py_off_t_converter(offset_src_obj, &offset_src)) + return NULL; + p_offset_src = &offset_src; + } + if (offset_dst_obj == Py_None) + p_offset_dst = NULL; + else { + if (!Py_off_t_converter(offset_dst_obj, &offset_dst)) + return NULL; + p_offset_dst = &offset_dst; + } + + do { + Py_BEGIN_ALLOW_THREADS + ret = copy_file_range(src, p_offset_src, dst, p_offset_dst, count, flags); + Py_END_ALLOW_THREADS + } while (ret < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); + + if (ret < 0) + return async_err ? NULL : posix_error(); + + /* we currently ignore the changes to the offsets */ + return Py_BuildValue("n", ret); +} +#endif /* HAVE_COPY_FILE_RANGE */ /*[clinic input] os.fstat @@ -12467,6 +12541,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 2fcfa9badfcd configure.ac --- a/configure.ac Wed Jun 08 13:32:49 2016 +0000 +++ b/configure.ac Thu Jun 09 13:49:59 2016 +0200 @@ -3298,6 +3298,12 @@ [#include #include ]) +AC_CHECK_DECL(__NR_copy_file_range, + AC_DEFINE(HAVE_COPY_FILE_RANGE, 1, + Define if you have the 'copy_file_range' syscall.), , + [#include + #include ]) + # For some functions, having a definition is not sufficient, since # we want to take their address. AC_MSG_CHECKING(for chroot)