copy_file_range: New function to copy file data

Message ID 20171117145604.655D24469B999@oldenburg.str.redhat.com
State Superseded
Headers

Commit Message

Florian Weimer Nov. 17, 2017, 2:56 p.m. UTC
  The semantics are based on the Linux system call, but a very close
emulation in user space is provided.

2017-11-17  Florian Weimer  <fweimer@redhat.com>

	* io/Makefile (routines): Add copy_file_range.
	(tests): Add tst-copy_file_range.
	(tests-static, tests-internal): Add tst-copy_file_range-compat.
	* io/Versions (GLIBC_2.27): Export copy_file_range.
	* io/copy_file_range-common.c: New file.
	* io/copy_file_range.c: Likewise.
	* io/tst-copy_file_range-compat.c: Likewise.
	* io/tst-copy_file_range.c: Likewise.
	* manual/llio.texi (Copying File Data): New section.
	* posix/unistd.h [__USE_GNU] (copy_file_range): Declare.
	* support/Makefile (libsupport-routines): Add support-xfstat,
	support_enter_mount_namespace, xftruncate, xlseek.
	* support/namespace.h (support_enter_mount_namespace): Declare.
	* support/support-xfstat.c: New file.
	* support/support_enter_mount_namespace.c: Likewise.
	* support/xftruncate.c: Likewise.
	* support/xlseek.c: Likewise.
	* sysdeps/unix/sysv/linux/**.abilist: Update.
	* sysdeps/unix/sysv/linux/copy_file_range.c: New file.
  

Comments

Florian Weimer Nov. 17, 2017, 2:58 p.m. UTC | #1
On 11/17/2017 03:56 PM, Florian Weimer wrote:
> 	* io/tst-copy_file_range.c: Likewise.

The test depends on TEST_COMPARE from the MPK patch.

> 	* support/Makefile (libsupport-routines): Add support-xfstat,
> 	support_enter_mount_namespace, xftruncate, xlseek.
> 	* support/namespace.h (support_enter_mount_namespace): Declare.
> 	* support/support-xfstat.c: New file.
> 	* support/support_enter_mount_namespace.c: Likewise.
> 	* support/xftruncate.c: Likewise.
> 	* support/xlseek.c: Likewise.

As usual, I can commit the support/ additions separately if this is 
desirable.

Thanks,
Florian
  
Rical Jasan Nov. 19, 2017, 3:17 a.m. UTC | #2
On 11/17/2017 06:56 AM, Florian Weimer wrote:
> diff --git a/manual/llio.texi b/manual/llio.texi
> index 825fd94e32..d56dadae12 100644
> --- a/manual/llio.texi
> +++ b/manual/llio.texi
> @@ -41,6 +41,7 @@ directly.)
>  * Stream/Descriptor Precautions::       Precautions needed if you use both
>                                           descriptors and streams.
>  * Scatter-Gather::                      Fast I/O to discontinuous buffers.
> +* Copying File Data::                   Copying data between files.
>  * Memory-mapped I/O::                   Using files like memory.
>  * Waiting for I/O::                     How to check for input or output
>  					 on multiple file descriptors.
> @@ -1363,6 +1364,80 @@ may be easier to use than these functions.  However, @code{readv} and
>  (as opposed to the total output), are large.  In that case, a high-level
>  stream would not be able to cache the data efficiently.
>  
> +@node Copying File Data
> +@section Copying data between two files
> +@cindex copying files
> +@cindex file copy
> +
> +A special function is provided to copy data between two files on the
> +same file system.  The system can optimize such copy operations.  This
> +is particularly important on network file systems, where the data would
> +otherwise have to be transferred twice over the network.
> +
> +Note that this function only copies file data, but not metadata such as
> +file permissions or extended attributes.
> +
> +@deftypefun ssize_t copy_file_range (int @var{inputfd}, off64_t *@var{inputpos}, int @var{outputfd}, off64_t *@var{outputpos}, ssize_t @var{length}, unsigned int @var{flags})
> +@standards{GNU, unistd.h}
> +@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
> +
> +This function copies up to @var{length} bytes from the file descriptor
> +@var{inputfd} to the file descriptor @var{outputfd}.
> +
> +The function can operate on both the current file position (like
> +@code{read} and @code{write}) and an explicit offset (like @code{pread}
> +and @code{pwrite}).  If the @var{inputpos} pointer is null, the file
> +position of @var{inputfd} is used as the starting point of the copy
> +operation, and the file position is advanced during it.  If
> +@var{inputpos} is not null, then @code{*@var{inputpos}} is used as the
> +starting point of the copy operation, and @code{*@var{inputpos}} is
> +incremented by the number of copied bytes, but the file position remains
> +unchanged.  Similar rules apply to @var{outputfd} and @var{outputpos}
> +for the output file position.
> +
> +The @var{flags} argument is currently reserved and must be zero.
> +
> +The @code{copy_file_range} function returns the number of bytes copied.
> +This can be less than the specified @var{length} in case the input file
> +contains fewer remaining bytes than @var{length}, or if a read or write
> +failure occurs.  The return value is zero if the end of the input file
> +is encountered immediately.
> +
> +If no bytes can be copied, to report an error, @code{copy_file_range}
> +returns the value @math{-1} and sets @code{errno}.  The following

Perhaps others will weigh in, but I lean towards @code for return
values.  The manual clearly doesn't have a preference at this point,
though, and uses both heavily.

> +@code{errno} error conditions are specific to this function.

"function:"

> +
> +@table @code
> +@item EISDIR
> +At least one of the descriptors @var{inputfd} or @var{outputfd} refers
> +to a directory.
> +
> +@item EINVAL
> +At least one of the descriptors @var{inputfd} or @var{outputfd} refers
> +to a non-regular, non-directory file (such as a socket or a FIFO).
> +
> +The @var{flags} argument is not zero.
> +
> +@item EBADF
> +The argument @var{inputfd} is not a valid file descriptor open for
> +reading.
> +
> +The argument @var{outputfd} is not a valid file descriptor open for
> +writing, or @var{outputfd} has been opened with @code{O_APPEND}.
> +
> +@item EXDEV
> +The input and output files reside on different file systems.
> +@end table
> +
> +In addition, @code{copy_file_range} can result with the error codes

"can result in" (or maybe change "result" to something different)

> +which are used by @code{read}, @code{pread}, @code{write}, and
> +@code{pwrite}.
> +
> +The @code{copy_file_range} is a cancellation point.  In case of

"The ... function" (or drop "The")

> +cancellation, the input location (the file position or the value at
> +@code{@var{inputpos}}) is indeterminate.

Did you mean @code{*@var{inputpos}}?  (That isn't a stamp of approval on
how we should format dereferencing; I've been arguing with myself over
that for a long time, but this form at least has precedent, so is fine.)

Thank you,
Rical
  
Rical Jasan Nov. 19, 2017, 3:20 a.m. UTC | #3
On 11/17/2017 06:58 AM, Florian Weimer wrote:
> As usual, I can commit the support/ additions separately if this is
> desirable.

I would like to see those sorts of changes separated out.  It keeps the
commits cleaner, and patches a little easier to review.

Rical
  
Florian Weimer Nov. 20, 2017, 12:55 p.m. UTC | #4
On 11/19/2017 04:20 AM, Rical Jasan wrote:
> On 11/17/2017 06:58 AM, Florian Weimer wrote:
>> As usual, I can commit the support/ additions separately if this is
>> desirable.
> 
> I would like to see those sorts of changes separated out.  It keeps the
> commits cleaner, and patches a little easier to review.

I brought this up earlier this month and received the opposite feedback:

   <https://sourceware.org/ml/libc-alpha/2017-11/msg00097.html>

Florian
  
Rical Jasan Nov. 21, 2017, 12:48 a.m. UTC | #5
On 11/20/2017 04:55 AM, Florian Weimer wrote:
> On 11/19/2017 04:20 AM, Rical Jasan wrote:
>> On 11/17/2017 06:58 AM, Florian Weimer wrote:
>>> As usual, I can commit the support/ additions separately if this is
>>> desirable.
>>
>> I would like to see those sorts of changes separated out.  It keeps the
>> commits cleaner, and patches a little easier to review.
> 
> I brought this up earlier this month and received the opposite feedback:
> 
>   <https://sourceware.org/ml/libc-alpha/2017-11/msg00097.html>

Ah, well, who am I to complain?  Carry on.  :)

Rical
  
Florian Weimer Nov. 23, 2017, 2:46 p.m. UTC | #6
On 11/19/2017 04:17 AM, Rical Jasan wrote:

>> +@code{errno} error conditions are specific to this function.
> 
> "function:"

Fixed.

>> +In addition, @code{copy_file_range} can result with the error codes
> 
> "can result in" (or maybe change "result" to something different)

I meant to write “can fail with”.  Fixed.

> 
>> +which are used by @code{read}, @code{pread}, @code{write}, and
>> +@code{pwrite}.
>> +
>> +The @code{copy_file_range} is a cancellation point.  In case of
> 
> "The ... function" (or drop "The")

Fixed.

>> +cancellation, the input location (the file position or the value at
>> +@code{@var{inputpos}}) is indeterminate.
> 
> Did you mean @code{*@var{inputpos}}?  (That isn't a stamp of approval on
> how we should format dereferencing; I've been arguing with myself over
> that for a long time, but this form at least has precedent, so is fine.)

Yes, the asterisk was missing.

Thank you for your documentation review.

Does anyone want to comment on the implementation? 8-)

Thanks,
Florian
  
Andreas Schwab Nov. 23, 2017, 3:13 p.m. UTC | #7
On Nov 17 2017, fweimer@redhat.com (Florian Weimer) wrote:

> +COPY_FILE_RANGE_DECL
> +ssize_t
> +COPY_FILE_RANGE (int infd, __off64_t *pinoff,
> +                 int outfd, __off64_t *poutoff,
> +                 size_t length, unsigned int flags)
> +{
> +  if (flags != 0)
> +    {
> +      __set_errno (EINVAL);
> +      return -1;
> +    }
> +
> +  struct stat64 instat;
> +  struct stat64 outstat;
> +  if (fstat64 (infd, &instat) != 0 || fstat64 (outfd, &outstat))

!= 0

> +      /* Write the buffer part which was read to the destination.  */
> +      char *end = buf + read_count;
> +      for (char *p = buf; p < end; )
> +        {
> +          ssize_t write_count;
> +          if (poutoff == NULL)
> +            write_count = write (outfd, p, end - p);
> +          else
> +            write_count = __libc_pwrite64 (outfd, p, end - p, *poutoff);
> +          if (write_count == 0)
> +            {
> +              /* Assume that this means no space on the target file
> +                 system, and use the error handling path below.  */

I don't think write can ever return 0 when writing more than zero bytes.

Andreas.
  
Florian Weimer Nov. 23, 2017, 3:23 p.m. UTC | #8
On 11/23/2017 04:13 PM, Andreas Schwab wrote:
> On Nov 17 2017, fweimer@redhat.com (Florian Weimer) wrote:
> 
>> +COPY_FILE_RANGE_DECL
>> +ssize_t
>> +COPY_FILE_RANGE (int infd, __off64_t *pinoff,
>> +                 int outfd, __off64_t *poutoff,
>> +                 size_t length, unsigned int flags)
>> +{
>> +  if (flags != 0)
>> +    {
>> +      __set_errno (EINVAL);
>> +      return -1;
>> +    }
>> +
>> +  struct stat64 instat;
>> +  struct stat64 outstat;
>> +  if (fstat64 (infd, &instat) != 0 || fstat64 (outfd, &outstat))
> 
> != 0

Thanks, fixed.

>> +      /* Write the buffer part which was read to the destination.  */
>> +      char *end = buf + read_count;
>> +      for (char *p = buf; p < end; )
>> +        {
>> +          ssize_t write_count;
>> +          if (poutoff == NULL)
>> +            write_count = write (outfd, p, end - p);
>> +          else
>> +            write_count = __libc_pwrite64 (outfd, p, end - p, *poutoff);
>> +          if (write_count == 0)
>> +            {
>> +              /* Assume that this means no space on the target file
>> +                 system, and use the error handling path below.  */
> 
> I don't think write can ever return 0 when writing more than zero bytes.

I can drop the check.  With the Linux VFS layer, it is difficult to tell 
whether this condition can ever happen, and if it does, we would likely 
enter an infinite loop without the check.

Thanks,
Florian
  
Andreas Schwab Nov. 23, 2017, 3:39 p.m. UTC | #9
On Nov 23 2017, Florian Weimer <fweimer@redhat.com> wrote:

>> I don't think write can ever return 0 when writing more than zero bytes.
>
> I can drop the check.  With the Linux VFS layer, it is difficult to tell
> whether this condition can ever happen, and if it does, we would likely
> enter an infinite loop without the check.

Or we get the real error in the next loop.

Andreas.
  
Florian Weimer Nov. 23, 2017, 3:43 p.m. UTC | #10
On 11/23/2017 04:39 PM, Andreas Schwab wrote:
> On Nov 23 2017, Florian Weimer <fweimer@redhat.com> wrote:
> 
>>> I don't think write can ever return 0 when writing more than zero bytes.
>>
>> I can drop the check.  With the Linux VFS layer, it is difficult to tell
>> whether this condition can ever happen, and if it does, we would likely
>> enter an infinite loop without the check.
>  > Or we get the real error in the next loop.

Tradition has it that it's a replacement for the ENOSPC condition, and 
that would be sticky and happen again in the next loop iteration.

Thanks,
Florian
  
Andreas Schwab Nov. 23, 2017, 3:46 p.m. UTC | #11
On Nov 23 2017, Florian Weimer <fweimer@redhat.com> wrote:

> On 11/23/2017 04:39 PM, Andreas Schwab wrote:
>> On Nov 23 2017, Florian Weimer <fweimer@redhat.com> wrote:
>>
>>>> I don't think write can ever return 0 when writing more than zero bytes.
>>>
>>> I can drop the check.  With the Linux VFS layer, it is difficult to tell
>>> whether this condition can ever happen, and if it does, we would likely
>>> enter an infinite loop without the check.
>>  > Or we get the real error in the next loop.
>
> Tradition has it that it's a replacement for the ENOSPC condition,

A short write, yes, but not a zero write.

Andreas.
  
Florian Weimer Nov. 23, 2017, 4:23 p.m. UTC | #12
On 11/23/2017 04:46 PM, Andreas Schwab wrote:
> On Nov 23 2017, Florian Weimer <fweimer@redhat.com> wrote:
> 
>> On 11/23/2017 04:39 PM, Andreas Schwab wrote:
>>> On Nov 23 2017, Florian Weimer <fweimer@redhat.com> wrote:
>>>
>>>>> I don't think write can ever return 0 when writing more than zero bytes.
>>>>
>>>> I can drop the check.  With the Linux VFS layer, it is difficult to tell
>>>> whether this condition can ever happen, and if it does, we would likely
>>>> enter an infinite loop without the check.
>>>   > Or we get the real error in the next loop.
>>
>> Tradition has it that it's a replacement for the ENOSPC condition,
> 
> A short write, yes, but not a zero write.

Looking at _IO_new_file_write in libio/fileops.c (which is where a call 
to fwrite eventually ends up if the buffer is full):

   _IO_ssize_t to_do = n;
   while (to_do > 0)
     {
       _IO_ssize_t count = (__builtin_expect (f->_flags2
					     & _IO_FLAGS2_NOTCANCEL, 0)
			   ? __write_nocancel (f->_fileno, data, to_do)
			   : __write (f->_fileno, data, to_do));
       if (count < 0)
	{
	  f->_flags |= _IO_ERR_SEEN;
	  break;
	}
       to_do -= count;
       data = (void *) ((char *) data + count);
     }

I see that we do not have a count == 0 special case there, so I'm going 
to drop the check from copy_file_range, as you proposed.

Thanks,
Florian
  

Patch

diff --git a/io/Makefile b/io/Makefile
index 2f26bf56db..f2f0f0d040 100644
--- a/io/Makefile
+++ b/io/Makefile
@@ -52,7 +52,7 @@  routines :=								\
 	ftw ftw64 fts fts64 poll ppoll					\
 	posix_fadvise posix_fadvise64					\
 	posix_fallocate posix_fallocate64				\
-	sendfile sendfile64 \
+	sendfile sendfile64 copy_file_range 				\
 	utimensat futimens
 
 # These routines will be omitted from the libc shared object.
@@ -70,7 +70,13 @@  tests		:= test-utime test-stat test-stat2 test-lfs tst-getcwd \
 		   tst-symlinkat tst-linkat tst-readlinkat tst-mkdirat \
 		   tst-mknodat tst-mkfifoat tst-ttyname_r bug-ftw5 \
 		   tst-posix_fallocate tst-posix_fallocate64 \
-		   tst-fts tst-fts-lfs tst-open-tmpfile
+		   tst-fts tst-fts-lfs tst-open-tmpfile \
+		   tst-copy_file_range \
+
+# This test includes the compat implementation of copy_file_range,
+# which uses internal, unexported libc functions.
+tests-static += tst-copy_file_range-compat
+tests-internal += tst-copy_file_range-compat
 
 ifeq ($(run-built-tests),yes)
 tests-special += $(objpfx)ftwtest.out
diff --git a/io/Versions b/io/Versions
index 64316cd025..98898cb9d5 100644
--- a/io/Versions
+++ b/io/Versions
@@ -125,4 +125,7 @@  libc {
   GLIBC_2.23 {
     fts64_children; fts64_close; fts64_open; fts64_read; fts64_set;
   }
+  GLIBC_2.27 {
+    copy_file_range;
+  }
 }
diff --git a/io/copy_file_range-common.c b/io/copy_file_range-common.c
new file mode 100644
index 0000000000..84fc01ef57
--- /dev/null
+++ b/io/copy_file_range-common.c
@@ -0,0 +1,163 @@ 
+/* Emulation of copy_file_range.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+/* The following macros should be defined before including this
+   file:
+
+   COPY_FILE_RANGE_DECL   Declaration specifiers for the function below.
+   COPY_FILE_RANGE        Name of the function to define.  */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+COPY_FILE_RANGE_DECL
+ssize_t
+COPY_FILE_RANGE (int infd, __off64_t *pinoff,
+                 int outfd, __off64_t *poutoff,
+                 size_t length, unsigned int flags)
+{
+  if (flags != 0)
+    {
+      __set_errno (EINVAL);
+      return -1;
+    }
+
+  struct stat64 instat;
+  struct stat64 outstat;
+  if (fstat64 (infd, &instat) != 0 || fstat64 (outfd, &outstat))
+    return -1;
+  if (S_ISDIR (instat.st_mode) || S_ISDIR (outstat.st_mode))
+    {
+      __set_errno (EISDIR);
+      return -1;
+    }
+  if (!S_ISREG (instat.st_mode) || !S_ISREG (outstat.st_mode))
+    {
+      /* We need a regular input file so that the we can seek
+         backwards in case of a write failure.  */
+      __set_errno (EINVAL);
+      return -1;
+    }
+  if (instat.st_dev != outstat.st_dev)
+    {
+      /* Cross-device copies are not supported.  */
+      __set_errno (EXDEV);
+      return -1;
+    }
+
+  /* The output descriptor must not have O_APPEND set.  */
+  {
+    int flags = __fcntl (outfd, F_GETFL);
+    if (flags & O_APPEND)
+      {
+        __set_errno (EBADF);
+        return -1;
+      }
+  }
+
+  /* Avoid an overflow in the result.  */
+  if (length > SSIZE_MAX)
+    length = SSIZE_MAX;
+
+  /* Main copying loop.  The buffer size is arbitrary and is a
+     trade-off between stack size consumption, cache usage, and
+     amortization of system call overhead.  */
+  size_t copied = 0;
+  char buf[8192];
+  while (length > 0)
+    {
+      size_t to_read = length;
+      if (to_read > sizeof (buf))
+        to_read = sizeof (buf);
+
+      /* Fill the buffer.  */
+      ssize_t read_count;
+      if (pinoff == NULL)
+        read_count = read (infd, buf, to_read);
+      else
+        read_count = __libc_pread64 (infd, buf, to_read, *pinoff);
+      if (read_count == 0)
+        /* End of file reached prematurely.  */
+        return copied;
+      if (read_count < 0)
+        {
+          if (copied > 0)
+            /* Report the number of bytes copied so far.  */
+            return copied;
+          return -1;
+        }
+      if (pinoff != 0)
+        *pinoff += read_count;
+
+      /* Write the buffer part which was read to the destination.  */
+      char *end = buf + read_count;
+      for (char *p = buf; p < end; )
+        {
+          ssize_t write_count;
+          if (poutoff == NULL)
+            write_count = write (outfd, p, end - p);
+          else
+            write_count = __libc_pwrite64 (outfd, p, end - p, *poutoff);
+          if (write_count == 0)
+            {
+              /* Assume that this means no space on the target file
+                 system, and use the error handling path below.  */
+              __set_errno (ENOSPC);
+              write_count = -1;
+            }
+          if (write_count < 0)
+            {
+              /* Adjust the input read position to match what we have
+                 written, so that the caller can pick up after the
+                 error.  */
+              size_t written = p - buf;
+              size_t overread = read_count - written;
+              if (pinoff == NULL)
+                {
+                  if (overread > 0)
+                    {
+                      /* We are on an error recovery path, so we
+                         cannot deal with failure here.  */
+                      int save_errno = errno;
+                      (void) __libc_lseek64 (infd, -overread, SEEK_CUR);
+                      __set_errno (save_errno);
+                    }
+                }
+              else /* pinoff != NULL */
+                *pinoff -= overread;
+
+              if (copied > 0)
+                /* Report the number of bytes copied so far.  */
+                return copied + written;
+              return -1;
+            }
+          p += write_count;
+          if (poutoff != NULL)
+            *poutoff += write_count;
+        } /* Write loop.  */
+
+      copied += read_count;
+      length -= read_count;
+    }
+  return copied;
+}
diff --git a/io/copy_file_range.c b/io/copy_file_range.c
new file mode 100644
index 0000000000..fdb6317619
--- /dev/null
+++ b/io/copy_file_range.c
@@ -0,0 +1,22 @@ 
+/* Generic implementation of copy_file_range.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#define COPY_FILE_RANGE_DECL
+#define COPY_FILE_RANGE copy_file_range
+
+#include <io/copy_file_range-common.c>
diff --git a/io/tst-copy_file_range-compat.c b/io/tst-copy_file_range-compat.c
new file mode 100644
index 0000000000..873b6980b1
--- /dev/null
+++ b/io/tst-copy_file_range-compat.c
@@ -0,0 +1,30 @@ 
+/* Test the fallback implementation of copy_file_range.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+/* Get the declaration of the official copy_of_range function.  */
+#include <unistd.h>
+
+/* Compile a local version of copy_file_range.  */
+#define COPY_FILE_RANGE_DECL static
+#define COPY_FILE_RANGE copy_file_range_compat
+#include <io/copy_file_range-common.c>
+
+/* Re-use the test, but run it against copy_file_range_compat defined
+   above.  */
+#define copy_file_range copy_file_range_compat
+#include "tst-copy_file_range.c"
diff --git a/io/tst-copy_file_range.c b/io/tst-copy_file_range.c
new file mode 100644
index 0000000000..af81d42817
--- /dev/null
+++ b/io/tst-copy_file_range.c
@@ -0,0 +1,729 @@ 
+/* Tests for copy_file_range.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <array_length.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <libgen.h>
+#include <poll.h>
+#include <sched.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/namespace.h>
+#include <support/support.h>
+#include <support/temp_file.h>
+#include <support/test-driver.h>
+#include <support/xunistd.h>
+#include <sys/mount.h>
+
+/* Boolean flags which indicate whether to use pointers with explicit
+   output flags.  */
+static int do_inoff;
+static int do_outoff;
+
+/* Name and descriptors of the input files.  Files are truncated and
+   reopened (with O_RDWR) between tests.  */
+static char *infile;
+static int infd;
+static char *outfile;
+static int outfd;
+
+/* Like the above, but on a different file system.  xdevfile can be
+   NULL if no suitable file system has been found.  */
+static char *xdevfile;
+
+/* Input and output offsets.  Set according to do_inoff and do_outoff
+   before the test.  The offsets themselves are always set to
+   zero.  */
+static off64_t inoff;
+static off64_t *pinoff;
+static off64_t outoff;
+static off64_t *poutoff;
+
+/* These are a collection of copy sizes used in tests.  The selection
+   takes into account that the fallback implementation uses an
+   internal buffer of 8192 bytes.  */
+enum { maximum_size = 99999 };
+static const int typical_sizes[] =
+  { 0, 1, 2, 3, 1024, 2048, 4096, 8191, 8192, 8193, 16383, 16384, 16385,
+    maximum_size };
+
+/* The random contents of this array can be used as a pattern to check
+   for correct write operations.  */
+static unsigned char random_data[maximum_size];
+
+/* The size chosen by the test harness.  */
+static int current_size;
+
+/* Discover the maximum file size (writable offset) for outfd.
+   Truncate outfd to length zero as a side effect.  */
+static off64_t
+find_maximum_offset (void)
+{
+  xftruncate (outfd, 0);
+  static off64_t cache = 0;
+  if (cache != 0)
+    return cache;
+
+  uint64_t upper = -1;
+  upper >>= 1;                  /* Maximum of off64_t.  */
+  TEST_VERIFY ((off64_t) upper > 0);
+  TEST_VERIFY ((off64_t) (upper + 1) < 0);
+  if (lseek (outfd, upper, SEEK_SET) >= 0)
+    {
+      if (write (outfd, "", 1) == 1)
+        FAIL_EXIT1 ("created a file larger than the off64_t range");
+    }
+
+  uint64_t lower = 1024 * 1024; /* A reasonable minimum file size.  */
+  /* Loop invariant: writing at lower succeeds, writing at upper fails.  */
+  while (lower + 1 < upper)
+    {
+      uint64_t middle = (lower + upper) / 2;
+      if (test_verbose > 0)
+        printf ("info: %s: remaining test range %" PRIu64 " .. %" PRIu64
+                ", probe at %" PRIu64 "\n", __func__, lower, upper, middle);
+      xftruncate (outfd, 0);
+      if (lseek (outfd, middle, SEEK_SET) >= 0
+          && write (outfd, "", 1) == 1)
+        lower = middle;
+      else
+        upper = middle;
+    }
+  TEST_VERIFY (lower + 1 == upper);
+  xftruncate (outfd, 0);
+  cache = lower;
+  printf ("info: maximum writable file offset: %" PRIu64 " (%" PRIx64 ")\n",
+          lower, lower);
+  return lower;
+}
+
+/* Perform a copy of a file.  */
+static void
+simple_file_copy (void)
+{
+  xwrite (infd, random_data, current_size);
+
+  int length;
+  int in_skipped; /* Expected skipped bytes in input.  */
+  if (do_inoff)
+    {
+      xlseek (infd, 1, SEEK_SET);
+      inoff = 2;
+      length = current_size - 3;
+      in_skipped = 2;
+    }
+  else
+    {
+      xlseek (infd, 3, SEEK_SET);
+      length = current_size - 5;
+      in_skipped = 3;
+    }
+  int out_skipped; /* Expected skipped bytes before the written data.  */
+  if (do_outoff)
+    {
+      xlseek (outfd, 4, SEEK_SET);
+      outoff = 5;
+      out_skipped = 5;
+    }
+  else
+    {
+      xlseek (outfd, 6, SEEK_SET);
+      length = current_size - 6;
+      out_skipped = 6;
+    }
+  if (length < 0)
+    length = 0;
+
+  TEST_COMPARE (copy_file_range (infd, pinoff, outfd, poutoff,
+                                 length, 0), length);
+  if (do_inoff)
+    {
+      TEST_COMPARE (inoff, 2 + length);
+      TEST_COMPARE (xlseek (infd, 0, SEEK_CUR), 1);
+    }
+  else
+    TEST_COMPARE (xlseek (infd, 0, SEEK_CUR), 3 + length);
+  if (do_outoff)
+    {
+      TEST_COMPARE (outoff, 5 + length);
+      TEST_COMPARE (xlseek (outfd, 0, SEEK_CUR), 4);
+    }
+  else
+    TEST_COMPARE (xlseek (outfd, 0, SEEK_CUR), 6 + length);
+
+  struct stat64 st;
+  xfstat (outfd, &st);
+  if (length > 0)
+    TEST_COMPARE (st.st_size, out_skipped + length);
+  else
+    {
+      /* If we did not write anything, we also did not add any
+         padding.  */
+      TEST_COMPARE (st.st_size, 0);
+      return;
+    }
+
+  xlseek (outfd, 0, SEEK_SET);
+  char *bytes = xmalloc (st.st_size);
+  TEST_COMPARE (read (outfd, bytes, st.st_size), st.st_size);
+  for (int i = 0; i < out_skipped; ++i)
+    TEST_COMPARE (bytes[i], 0);
+  TEST_VERIFY (memcmp (bytes + out_skipped, random_data + in_skipped,
+                       length) == 0);
+  free (bytes);
+}
+
+/* Test that reading from a pipe willfails.  */
+static void
+pipe_as_source (void)
+{
+  int pipefds[2];
+  xpipe (pipefds);
+
+  for (int length = 0; length < 2; ++length)
+    {
+      if (test_verbose > 0)
+        printf ("info: %s: length=%d\n", __func__, length);
+
+      /* Make sure that there is something to copy in the pipe.  */
+      xwrite (pipefds[1], "@", 1);
+
+      TEST_COMPARE (copy_file_range (pipefds[0], pinoff, outfd, poutoff,
+                                     length, 0), -1);
+      /* Linux 4.10 and later return EINVAL.  Older kernels return
+         EXDEV.  */
+      TEST_VERIFY (errno == EINVAL || errno == EXDEV);
+      TEST_COMPARE (inoff, 0);
+      TEST_COMPARE (outoff, 0);
+      TEST_COMPARE (xlseek (outfd, 0, SEEK_CUR), 0);
+
+      /* Make sure that nothing was read.  */
+      char buf = 'A';
+      TEST_COMPARE (read (pipefds[0], &buf, 1), 1);
+      TEST_COMPARE (buf, '@');
+    }
+
+  xclose (pipefds[0]);
+  xclose (pipefds[1]);
+}
+
+/* Test that writing to a pipe fails.  */
+static void
+pipe_as_destination (void)
+{
+  /* Make sure that there is something to read in the input file.  */
+  xwrite (infd, "abc", 3);
+  xlseek (infd, 0, SEEK_SET);
+
+  int pipefds[2];
+  xpipe (pipefds);
+
+  for (int length = 0; length < 2; ++length)
+    {
+      if (test_verbose > 0)
+        printf ("info: %s: length=%d\n", __func__, length);
+
+      TEST_COMPARE (copy_file_range (infd, pinoff, pipefds[1], poutoff,
+                                     length, 0), -1);
+      /* Linux 4.10 and later return EINVAL.  Older kernels return
+         EXDEV.  */
+      TEST_VERIFY (errno == EINVAL || errno == EXDEV);
+      TEST_COMPARE (inoff, 0);
+      TEST_COMPARE (outoff, 0);
+      TEST_COMPARE (xlseek (infd, 0, SEEK_CUR), 0);
+
+      /* Make sure that nothing was written.  */
+      struct pollfd pollfd = { .fd = pipefds[0], .events = POLLIN, };
+      TEST_COMPARE (poll (&pollfd, 1, 0), 0);
+    }
+
+  xclose (pipefds[0]);
+  xclose (pipefds[1]);
+}
+
+/* Test a write failure after (potentially) writing some bytes.  */
+static void
+delayed_write_failure (void)
+{
+  /* We need to write something to provoke the error.  */
+  if (current_size == 0)
+    return;
+  xwrite (infd, random_data, sizeof (random_data));
+  xlseek (infd, 0, SEEK_SET);
+
+  /* Write failure near the start.  */
+  off64_t where = find_maximum_offset ();
+  if (current_size == 1)
+    ++where;
+  outoff = where;
+  if (do_outoff)
+    xlseek (outfd, 1, SEEK_SET);
+  else
+    xlseek (outfd, where, SEEK_SET);
+  TEST_COMPARE (copy_file_range (infd, pinoff, outfd, poutoff,
+                                 sizeof (random_data), 0), -1);
+  TEST_COMPARE (errno, EINVAL);
+  TEST_COMPARE (xlseek (infd, 0, SEEK_CUR), 0);
+  if (do_outoff)
+    TEST_COMPARE (xlseek (outfd, 0, SEEK_CUR), 1);
+  else
+    TEST_COMPARE (xlseek (outfd, 0, SEEK_CUR), where);
+  TEST_COMPARE (outoff, where);
+  struct stat64 st;
+  xfstat (outfd, &st);
+  TEST_COMPARE (st.st_size, 0);
+
+  /* Write failure near the end.  */
+  if (current_size == 1)
+    /* This would be same as the first test because there is only one
+       byte.  */
+    return;
+  where = find_maximum_offset () - current_size + 1;
+  if (current_size == sizeof (random_data))
+    /* Otherwise we do not reach the non-writable byte.  */
+    ++where;
+  outoff = where;
+  if (do_outoff)
+    xlseek (outfd, 1, SEEK_SET);
+  else
+    xlseek (outfd, where, SEEK_SET);
+  ssize_t ret = copy_file_range (infd, pinoff, outfd, poutoff,
+                                 sizeof (random_data), 0);
+  if (ret < 0)
+    {
+      TEST_COMPARE (ret, -1);
+      TEST_COMPARE (errno, EINVAL);
+      xfstat (outfd, &st);
+      TEST_COMPARE (st.st_size, 0);
+    }
+  else
+    {
+      /* The first copy succeeded.  This happens in the emulation
+         because the internal buffer of limited size does not
+         necessarily cross the off64_t boundary on the first write
+         operation.  */
+      if (test_verbose > 0)
+        printf ("info:   copy_file_range (%zu) returned %zd\n",
+                sizeof (random_data), ret);
+      TEST_VERIFY (ret > 0);
+      TEST_VERIFY (ret < maximum_size);
+      xfstat (outfd, &st);
+      TEST_COMPARE (st.st_size, where + ret);
+      if (do_inoff)
+        {
+          TEST_COMPARE (inoff, ret);
+          TEST_COMPARE (xlseek (infd, 0, SEEK_CUR), 0);
+        }
+      else
+          TEST_COMPARE (xlseek (infd, 0, SEEK_CUR), ret);
+
+      char *buffer = xmalloc (ret);
+      TEST_COMPARE (pread (outfd, buffer, ret, where), ret);
+      TEST_VERIFY (memcmp (buffer, random_data, ret) == 0);
+      free (buffer);
+
+      /* The second copy fails.  */
+      TEST_COMPARE (copy_file_range (infd, pinoff, outfd, poutoff,
+                                     sizeof (random_data), 0), -1);
+      TEST_COMPARE (errno, EINVAL);
+    }
+}
+
+/* Test a write failure across devices.  */
+static void
+cross_device_failure (void)
+{
+  if (xdevfile == NULL)
+    /* Subtest not supported due to missing cross-device file.  */
+    return;
+
+  /* We need something to write.  */
+  xwrite (infd, random_data, sizeof (random_data));
+  xlseek (infd, 0, SEEK_SET);
+
+  int xdevfd = xopen (xdevfile, O_RDWR | O_LARGEFILE, 0);
+  TEST_COMPARE (copy_file_range (infd, pinoff, xdevfd, poutoff,
+                                 current_size, 0), -1);
+  TEST_COMPARE (errno, EXDEV);
+  TEST_COMPARE (xlseek (infd, 0, SEEK_CUR), 0);
+  struct stat64 st;
+  xfstat (xdevfd, &st);
+  TEST_COMPARE (st.st_size, 0);
+
+  xclose (xdevfd);
+}
+
+/* Try to exercise ENOSPC behavior with a tempfs file system (so that
+   we do not have to fill up a regular file system to get the error).
+   This function runs in a subprocess, so that we do not change the
+   mount namespace of the actual test process.  */
+static void
+enospc_failure_1 (void *closure)
+{
+#ifdef CLONE_NEWNS
+  support_become_root ();
+
+  /* Make sure that we do not alter the file system mounts of the
+     parents.  */
+  if (! support_enter_mount_namespace ())
+    {
+      printf ("warning: ENOSPC test skipped\n");
+      return;
+    }
+
+  char *mountpoint = closure;
+  if (mount ("none", mountpoint, "tmpfs", MS_NODEV | MS_NOEXEC,
+             "size=500k") != 0)
+    {
+      printf ("warning: could not mount tmpfs at %s: %m\n", mountpoint);
+      return;
+    }
+
+  /* The source file must reside on the same file system.  */
+  char *intmpfsfile = xasprintf ("%s/%s", mountpoint, "in");
+  int intmpfsfd = xopen (intmpfsfile, O_RDWR | O_CREAT | O_LARGEFILE, 0600);
+  xwrite (intmpfsfd, random_data, sizeof (random_data));
+  xlseek (intmpfsfd, 1, SEEK_SET);
+  inoff = 1;
+
+  char *outtmpfsfile = xasprintf ("%s/%s", mountpoint, "out");
+  int outtmpfsfd = xopen (outtmpfsfile, O_RDWR | O_CREAT | O_LARGEFILE, 0600);
+
+  /* Fill the file with data until ENOSPC is reached.  */
+  while (true)
+    {
+      ssize_t ret = write (outtmpfsfd, random_data, sizeof (random_data));
+      if (ret < 0 && errno != ENOSPC)
+        FAIL_EXIT1 ("write to %s: %m", outtmpfsfile);
+      if (ret < sizeof (random_data))
+        break;
+    }
+  TEST_COMPARE (write (outtmpfsfd, "", 1), -1);
+  TEST_COMPARE (errno, ENOSPC);
+  off64_t maxsize = xlseek (outtmpfsfd, 0, SEEK_CUR);
+  TEST_VERIFY_EXIT (maxsize > sizeof (random_data));
+
+  /* Constructed the expected file contents.  */
+  char *expected = xmalloc (maxsize);
+  TEST_COMPARE (pread (outtmpfsfd, expected, maxsize, 0), maxsize);
+  /* Go back a little, so some bytes can be written.  */
+  enum { offset = 20000 };
+  TEST_VERIFY_EXIT (offset < maxsize);
+  TEST_VERIFY_EXIT (offset < sizeof (random_data));
+  memcpy (expected + maxsize - offset, random_data + 1, offset);
+
+  if (do_outoff)
+    {
+      outoff = maxsize - offset;
+      xlseek (outtmpfsfd, 2, SEEK_SET);
+    }
+  else
+    xlseek (outtmpfsfd, -offset, SEEK_CUR);
+
+  /* First call is expected to succeed because we made room for some
+     bytes.  */
+  TEST_COMPARE (copy_file_range (intmpfsfd, pinoff, outtmpfsfd, poutoff,
+                                 maximum_size, 0), offset);
+  if (do_inoff)
+    {
+      TEST_COMPARE (inoff, 1 + offset);
+      TEST_COMPARE (xlseek (intmpfsfd, 0, SEEK_CUR), 1);
+    }
+  else
+      TEST_COMPARE (xlseek (intmpfsfd, 0, SEEK_CUR), 1 + offset);
+  if (do_outoff)
+    {
+      TEST_COMPARE (outoff, maxsize);
+      TEST_COMPARE (xlseek (outtmpfsfd, 0, SEEK_CUR), 2);
+    }
+  else
+    TEST_COMPARE (xlseek (outtmpfsfd, 0, SEEK_CUR), maxsize);
+  struct stat64 st;
+  xfstat (outtmpfsfd, &st);
+  TEST_COMPARE (st.st_size, maxsize);
+  char *actual = xmalloc (st.st_size);
+  TEST_COMPARE (pread (outtmpfsfd, actual, st.st_size, 0), st.st_size);
+  TEST_VERIFY (memcmp (expected, actual, maxsize) == 0);
+
+  /* Second call should fail with ENOSPC.  */
+  TEST_COMPARE (copy_file_range (intmpfsfd, pinoff, outtmpfsfd, poutoff,
+                                 maximum_size, 0), -1);
+  TEST_COMPARE (errno, ENOSPC);
+
+  /* Offsets should be unchanged.  */
+  if (do_inoff)
+    {
+      TEST_COMPARE (inoff, 1 + offset);
+      TEST_COMPARE (xlseek (intmpfsfd, 0, SEEK_CUR), 1);
+    }
+  else
+    TEST_COMPARE (xlseek (intmpfsfd, 0, SEEK_CUR), 1 + offset);
+  if (do_outoff)
+    {
+      TEST_COMPARE (outoff, maxsize);
+      TEST_COMPARE (xlseek (outtmpfsfd, 0, SEEK_CUR), 2);
+    }
+  else
+    TEST_COMPARE (xlseek (outtmpfsfd, 0, SEEK_CUR), maxsize);
+  TEST_COMPARE (xlseek (outtmpfsfd, 0, SEEK_END), maxsize);
+  TEST_COMPARE (pread (outtmpfsfd, actual, maxsize, 0), maxsize);
+  TEST_VERIFY (memcmp (expected, actual, maxsize) == 0);
+
+  free (actual);
+  free (expected);
+
+  xclose (intmpfsfd);
+  xclose (outtmpfsfd);
+  free (intmpfsfile);
+  free (outtmpfsfile);
+
+#else /* !CLONE_NEWNS */
+  puts ("warning: ENOSPC test skipped (no mount namespaces)");
+#endif
+}
+
+/* Call enospc_failure_1 in a subprocess.  */
+static void
+enospc_failure (void)
+{
+  char *mountpoint
+    = support_create_temp_directory ("tst-copy_file_range-enospc-");
+  support_isolate_in_subprocess (enospc_failure_1, mountpoint);
+  free (mountpoint);
+}
+
+/* The target file descriptor must have O_APPEND enabled.  */
+static void
+oappend_failure (void)
+{
+  /* Add data, to make sure we do not fail because there is
+     insufficient input data.  */
+  xwrite (infd, random_data, current_size);
+  xlseek (infd, 0, SEEK_SET);
+
+  xclose (outfd);
+  outfd = xopen (outfile, O_RDWR | O_APPEND, 0);
+  TEST_COMPARE (copy_file_range (infd, pinoff, outfd, poutoff,
+                                 current_size, 0), -1);
+  TEST_COMPARE (errno, EBADF);
+}
+
+/* Test that a short input file results in a shortened copy.  */
+static void
+short_copy (void)
+{
+  if (current_size == 0)
+    /* Nothing to shorten.  */
+    return;
+
+  /* Two subtests, one with offset 0 and current_size - 1 bytes, and
+     another one with current_size bytes, but offset 1.  */
+  for (int shift = 0; shift < 2; ++shift)
+    {
+      if (test_verbose > 0)
+        printf ("info:   shift=%d\n", shift);
+      xftruncate (infd, 0);
+      xlseek (infd, 0, SEEK_SET);
+      xwrite (infd, random_data, current_size - !shift);
+
+      if (do_inoff)
+        {
+          inoff = shift;
+          xlseek (infd, 2, SEEK_SET);
+        }
+      else
+        {
+          inoff = 3;
+          xlseek (infd, shift, SEEK_SET);
+        }
+      ftruncate (outfd, 0);
+      xlseek (outfd, 0, SEEK_SET);
+      outoff = 0;
+
+      /* First call copies current_size - 1 bytes.  */
+      TEST_COMPARE (copy_file_range (infd, pinoff, outfd, poutoff,
+                                     current_size, 0), current_size - 1);
+      char *buffer = xmalloc (current_size);
+      TEST_COMPARE (pread (outfd, buffer, current_size, 0), current_size - 1);
+      TEST_VERIFY (memcmp (buffer, random_data + shift, current_size - 1)
+                   == 0);
+      free (buffer);
+
+      if (do_inoff)
+        {
+          TEST_COMPARE (inoff, current_size - 1 + shift);
+          TEST_COMPARE (xlseek (infd, 0, SEEK_CUR), 2);
+        }
+      else
+        TEST_COMPARE (xlseek (infd, 0, SEEK_CUR), current_size - 1 + shift);
+      if (do_outoff)
+        {
+          TEST_COMPARE (outoff, current_size - 1);
+          TEST_COMPARE (xlseek (outfd, 0, SEEK_CUR), 0);
+        }
+      else
+        TEST_COMPARE (xlseek (outfd, 0, SEEK_CUR), current_size - 1);
+
+      /* First call copies zero bytes.  */
+      TEST_COMPARE (copy_file_range (infd, pinoff, outfd, poutoff,
+                                     current_size, 0), 0);
+      /* And the offsets are unchanged.  */
+      if (do_inoff)
+        {
+          TEST_COMPARE (inoff, current_size - 1 + shift);
+          TEST_COMPARE (xlseek (infd, 0, SEEK_CUR), 2);
+        }
+      else
+        TEST_COMPARE (xlseek (infd, 0, SEEK_CUR), current_size - 1 + shift);
+      if (do_outoff)
+        {
+          TEST_COMPARE (outoff, current_size - 1);
+          TEST_COMPARE (xlseek (outfd, 0, SEEK_CUR), 0);
+        }
+      else
+        TEST_COMPARE (xlseek (outfd, 0, SEEK_CUR), current_size - 1);
+    }
+}
+
+/* A named test function.  */
+struct test_case
+{
+  const char *name;
+  void (*func) (void);
+  bool sizes; /* If true, call the test with different current_size values.  */
+};
+
+/* The available test cases.  */
+static struct test_case tests[] =
+  {
+    { "simple_file_copy", simple_file_copy, .sizes = true },
+    { "pipe_as_source", pipe_as_source, },
+    { "pipe_as_destination", pipe_as_destination, },
+    { "delayed_write_failure", delayed_write_failure, .sizes = true },
+    { "cross_device_failure", cross_device_failure, .sizes = true },
+    { "enospc_failure", enospc_failure, },
+    { "oappend_failure", oappend_failure, .sizes = true },
+    { "short_copy", short_copy, .sizes = true },
+  };
+
+static int
+do_test (void)
+{
+  for (unsigned char *p = random_data; p < array_end (random_data); ++p)
+    *p = rand () >> 24;
+
+  infd = create_temp_file ("tst-copy_file_range-in-", &infile);
+  xclose (create_temp_file ("tst-copy_file_range-out-", &outfile));
+
+  /* Try to find a different directory from the default input/output
+     file.  */
+  {
+    struct stat64 instat;
+    xfstat (infd, &instat);
+    static const char *const candidates[] =
+      { NULL, "/var/tmp", "/dev/shm" };
+    for (const char *const *c = candidates; c < array_end (candidates); ++c)
+      {
+        const char *path = *c;
+        char *to_free = NULL;
+        if (path == NULL)
+          {
+            to_free = xreadlink ("/proc/self/exe");
+            path = dirname (to_free);
+          }
+
+        struct stat64 cstat;
+        xstat (path, &cstat);
+        if (cstat.st_dev == instat.st_dev)
+          {
+            free (to_free);
+            continue;
+          }
+
+        printf ("info: using alternate temporary files directory: %s\n", path);
+        xdevfile = xasprintf ("%s/tst-copy_file_range-xdev-XXXXXX", path);
+        free (to_free);
+        break;
+      }
+    if (xdevfile != NULL)
+      {
+        int xdevfd = mkstemp (xdevfile);
+        if (xdevfd < 0)
+          FAIL_EXIT1 ("mkstemp (\"%s\"): %m", xdevfile);
+        struct stat64 xdevst;
+        xfstat (xdevfd, &xdevst);
+        TEST_VERIFY (xdevst.st_dev != instat.st_dev);
+        add_temp_file (xdevfile);
+        xclose (xdevfd);
+      }
+    else
+      puts ("warning: no alternate directory on different file system found");
+  }
+  xclose (infd);
+
+  for (do_inoff = 0; do_inoff < 2; ++do_inoff)
+    for (do_outoff = 0; do_outoff < 2; ++do_outoff)
+      for (struct test_case *test = tests; test < array_end (tests); ++test)
+        for (const int *size = typical_sizes;
+             size < array_end (typical_sizes); ++size)
+          {
+            current_size = *size;
+            if (test_verbose > 0)
+              printf ("info: %s do_inoff=%d do_outoff=%d current_size=%d\n",
+                      test->name, do_inoff, do_outoff, current_size);
+
+            inoff = 0;
+            if (do_inoff)
+              pinoff = &inoff;
+            else
+              pinoff = NULL;
+            outoff = 0;
+            if (do_outoff)
+              poutoff = &outoff;
+            else
+              poutoff = NULL;
+
+            infd = xopen (infile, O_RDWR | O_LARGEFILE, 0);
+            xftruncate (infd, 0);
+            outfd = xopen (outfile, O_RDWR | O_LARGEFILE, 0);
+            xftruncate (outfd, 0);
+
+            test->func ();
+
+            xclose (infd);
+            xclose (outfd);
+
+            if (!test->sizes)
+              /* Skip the other sizes unless they have been
+                 requested.  */
+              break;
+          }
+
+  free (infile);
+  free (outfile);
+  free (xdevfile);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/manual/llio.texi b/manual/llio.texi
index 825fd94e32..d56dadae12 100644
--- a/manual/llio.texi
+++ b/manual/llio.texi
@@ -41,6 +41,7 @@  directly.)
 * Stream/Descriptor Precautions::       Precautions needed if you use both
                                          descriptors and streams.
 * Scatter-Gather::                      Fast I/O to discontinuous buffers.
+* Copying File Data::                   Copying data between files.
 * Memory-mapped I/O::                   Using files like memory.
 * Waiting for I/O::                     How to check for input or output
 					 on multiple file descriptors.
@@ -1363,6 +1364,80 @@  may be easier to use than these functions.  However, @code{readv} and
 (as opposed to the total output), are large.  In that case, a high-level
 stream would not be able to cache the data efficiently.
 
+@node Copying File Data
+@section Copying data between two files
+@cindex copying files
+@cindex file copy
+
+A special function is provided to copy data between two files on the
+same file system.  The system can optimize such copy operations.  This
+is particularly important on network file systems, where the data would
+otherwise have to be transferred twice over the network.
+
+Note that this function only copies file data, but not metadata such as
+file permissions or extended attributes.
+
+@deftypefun ssize_t copy_file_range (int @var{inputfd}, off64_t *@var{inputpos}, int @var{outputfd}, off64_t *@var{outputpos}, ssize_t @var{length}, unsigned int @var{flags})
+@standards{GNU, unistd.h}
+@safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
+
+This function copies up to @var{length} bytes from the file descriptor
+@var{inputfd} to the file descriptor @var{outputfd}.
+
+The function can operate on both the current file position (like
+@code{read} and @code{write}) and an explicit offset (like @code{pread}
+and @code{pwrite}).  If the @var{inputpos} pointer is null, the file
+position of @var{inputfd} is used as the starting point of the copy
+operation, and the file position is advanced during it.  If
+@var{inputpos} is not null, then @code{*@var{inputpos}} is used as the
+starting point of the copy operation, and @code{*@var{inputpos}} is
+incremented by the number of copied bytes, but the file position remains
+unchanged.  Similar rules apply to @var{outputfd} and @var{outputpos}
+for the output file position.
+
+The @var{flags} argument is currently reserved and must be zero.
+
+The @code{copy_file_range} function returns the number of bytes copied.
+This can be less than the specified @var{length} in case the input file
+contains fewer remaining bytes than @var{length}, or if a read or write
+failure occurs.  The return value is zero if the end of the input file
+is encountered immediately.
+
+If no bytes can be copied, to report an error, @code{copy_file_range}
+returns the value @math{-1} and sets @code{errno}.  The following
+@code{errno} error conditions are specific to this function.
+
+@table @code
+@item EISDIR
+At least one of the descriptors @var{inputfd} or @var{outputfd} refers
+to a directory.
+
+@item EINVAL
+At least one of the descriptors @var{inputfd} or @var{outputfd} refers
+to a non-regular, non-directory file (such as a socket or a FIFO).
+
+The @var{flags} argument is not zero.
+
+@item EBADF
+The argument @var{inputfd} is not a valid file descriptor open for
+reading.
+
+The argument @var{outputfd} is not a valid file descriptor open for
+writing, or @var{outputfd} has been opened with @code{O_APPEND}.
+
+@item EXDEV
+The input and output files reside on different file systems.
+@end table
+
+In addition, @code{copy_file_range} can result with the error codes
+which are used by @code{read}, @code{pread}, @code{write}, and
+@code{pwrite}.
+
+The @code{copy_file_range} is a cancellation point.  In case of
+cancellation, the input location (the file position or the value at
+@code{@var{inputpos}}) is indeterminate.
+@end deftypefun
+
 @node Memory-mapped I/O
 @section Memory-mapped I/O
 
diff --git a/posix/unistd.h b/posix/unistd.h
index 32b0f4898f..65317c79fd 100644
--- a/posix/unistd.h
+++ b/posix/unistd.h
@@ -1105,7 +1105,12 @@  extern int lockf64 (int __fd, int __cmd, __off64_t __len) __wur;
        do __result = (long int) (expression);				      \
        while (__result == -1L && errno == EINTR);			      \
        __result; }))
-#endif
+
+/* Copy LENGTH bytes from INFD to OUTFD.  */
+ssize_t copy_file_range (int __infd, __off64_t *__pinoff,
+			 int __outfd, __off64_t *__poutoff,
+			 size_t __length, unsigned int __flags);
+#endif /* __USE_GNU */
 
 #if defined __USE_POSIX199309 || defined __USE_UNIX98
 /* Synchronize at least the data part of a file with the underlying
diff --git a/support/Makefile b/support/Makefile
index b2ea371135..f5a2344981 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -36,12 +36,14 @@  libsupport-routines = \
   oom_error \
   resolv_test \
   set_fortify_handler \
+  support-xfstat \
   support-xstat \
   support_become_root \
   support_can_chroot \
   support_capture_subprocess \
   support_capture_subprocess_check \
   support_chroot \
+  support_enter_mount_namespace \
   support_enter_network_namespace \
   support_format_address_family \
   support_format_addrinfo \
@@ -72,8 +74,10 @@  libsupport-routines = \
   xfclose \
   xfopen \
   xfork \
+  xftruncate \
   xgetsockname \
   xlisten \
+  xlseek \
   xmalloc \
   xmemstream \
   xmkdir \
diff --git a/support/namespace.h b/support/namespace.h
index 9eddb1a0e9..b5e2d1474a 100644
--- a/support/namespace.h
+++ b/support/namespace.h
@@ -51,6 +51,11 @@  bool support_can_chroot (void);
    has sufficient privileges.  */
 bool support_enter_network_namespace (void);
 
+/* Enter a mount namespace and mark / as private (not shared).  If
+   this function returns true, mount operations in this process will
+   not affect the host system afterwards.  */
+bool support_enter_mount_namespace (void);
+
 /* Return true if support_enter_network_namespace managed to enter a
    UTS namespace.  */
 bool support_in_uts_namespace (void);
diff --git a/support/support-xfstat.c b/support/support-xfstat.c
new file mode 100644
index 0000000000..4c8ee9142b
--- /dev/null
+++ b/support/support-xfstat.c
@@ -0,0 +1,28 @@ 
+/* fstat64 with error checking.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <support/check.h>
+#include <support/xunistd.h>
+#include <sys/stat.h>
+
+void
+xfstat (int fd, struct stat64 *result)
+{
+  if (fstat64 (fd, result) != 0)
+    FAIL_EXIT1 ("fstat64 (%d): %m", fd);
+}
diff --git a/support/support_enter_mount_namespace.c b/support/support_enter_mount_namespace.c
new file mode 100644
index 0000000000..6140692075
--- /dev/null
+++ b/support/support_enter_mount_namespace.c
@@ -0,0 +1,45 @@ 
+/* Enter a mount namespace.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <support/namespace.h>
+
+#include <sched.h>
+#include <stdio.h>
+#include <sys/mount.h>
+
+bool
+support_enter_mount_namespace (void)
+{
+#ifdef CLONE_NEWNS
+  if (unshare (CLONE_NEWNS) == 0)
+    {
+      /* On some systems, / is marked as MS_SHARED, which means that
+         mounts within the namespace leak to the rest of the system,
+         which is not what we want.  */
+      if (mount ("none", "/", NULL, MS_REC | MS_PRIVATE, NULL) != 0)
+        {
+          printf ("warning: making the mount namespace private failed: %m\n");
+          return false;
+        }
+      return true;
+    }
+  else
+    printf ("warning: unshare (CLONE_NEWNS) failed: %m\n");
+#endif /* CLONE_NEWNS */
+  return false;
+}
diff --git a/support/xftruncate.c b/support/xftruncate.c
new file mode 100644
index 0000000000..9c4e9e3050
--- /dev/null
+++ b/support/xftruncate.c
@@ -0,0 +1,27 @@ 
+/* ftruncate with error checking.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <support/check.h>
+#include <support/xunistd.h>
+
+void
+xftruncate (int fd, long long length)
+{
+  if (ftruncate64 (fd, length) != 0)
+    FAIL_EXIT1 ("ftruncate64 (%d, %lld): %m", fd, length);
+}
diff --git a/support/xlseek.c b/support/xlseek.c
new file mode 100644
index 0000000000..0a75a9f2e6
--- /dev/null
+++ b/support/xlseek.c
@@ -0,0 +1,29 @@ 
+/* lseek with error checking.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <support/check.h>
+#include <support/xunistd.h>
+
+long long
+xlseek (int fd, long long offset, int whence)
+{
+  long long result = lseek64 (fd, offset, whence);
+  if (result < 0)
+    FAIL_EXIT1 ("lseek64 (%d, %lld, %d): %m", fd, offset, whence);
+  return result;
+}
diff --git a/support/xunistd.h b/support/xunistd.h
index 00376f7aae..29da063c15 100644
--- a/support/xunistd.h
+++ b/support/xunistd.h
@@ -36,10 +36,13 @@  void xpipe (int[2]);
 void xdup2 (int, int);
 int xopen (const char *path, int flags, mode_t);
 void xstat (const char *path, struct stat64 *);
+void xfstat (int fd, struct stat64 *);
 void xmkdir (const char *path, mode_t);
 void xchroot (const char *path);
 void xunlink (const char *path);
 long xsysconf (int name);
+long long xlseek (int fd, long long offset, int whence);
+void xftruncate (int fd, long long length);
 
 /* Read the link at PATH.  The caller should free the returned string
    with free.  */
diff --git a/sysdeps/unix/sysv/linux/aarch64/libc.abilist b/sysdeps/unix/sysv/linux/aarch64/libc.abilist
index 85788be12b..6907889239 100644
--- a/sysdeps/unix/sysv/linux/aarch64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/aarch64/libc.abilist
@@ -2104,6 +2104,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/alpha/libc.abilist b/sysdeps/unix/sysv/linux/alpha/libc.abilist
index 3b463dacbe..de78d30d90 100644
--- a/sysdeps/unix/sysv/linux/alpha/libc.abilist
+++ b/sysdeps/unix/sysv/linux/alpha/libc.abilist
@@ -2015,6 +2015,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/arm/libc.abilist b/sysdeps/unix/sysv/linux/arm/libc.abilist
index a1315aef35..7b0c7afc23 100644
--- a/sysdeps/unix/sysv/linux/arm/libc.abilist
+++ b/sysdeps/unix/sysv/linux/arm/libc.abilist
@@ -105,6 +105,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/copy_file_range.c b/sysdeps/unix/sysv/linux/copy_file_range.c
new file mode 100644
index 0000000000..6bb301dfef
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/copy_file_range.c
@@ -0,0 +1,46 @@ 
+/* Linux implementation of copy_file_range.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <errno.h>
+#include <sysdep-cancel.h>
+#include <unistd.h>
+
+/* Include the fallback implementation.  */
+#ifndef __ASSUME_COPY_FILE_RANGE
+#define COPY_FILE_RANGE_DECL static
+#define COPY_FILE_RANGE copy_file_range_compat
+#include <io/copy_file_range-common.c>
+#endif
+
+ssize_t
+copy_file_range (int infd, __off64_t *pinoff,
+                 int outfd, __off64_t *poutoff,
+                 size_t length, unsigned int flags)
+{
+#ifdef __NR_copy_file_range
+  ssize_t ret = SYSCALL_CANCEL (copy_file_range, infd, pinoff, outfd, poutoff,
+                                length, flags);
+# ifndef __ASSUME_COPY_FILE_RANGE
+  if (ret == -1 && errno == ENOSYS)
+    ret = copy_file_range_compat (infd, pinoff, outfd, poutoff, length, flags);
+# endif
+  return ret;
+#else  /* !__NR_copy_file_range */
+  return copy_file_range_compat (infd, pinoff, outfd, poutoff, length, flags);
+#endif
+}
diff --git a/sysdeps/unix/sysv/linux/hppa/libc.abilist b/sysdeps/unix/sysv/linux/hppa/libc.abilist
index 7397d728f2..110ff052a6 100644
--- a/sysdeps/unix/sysv/linux/hppa/libc.abilist
+++ b/sysdeps/unix/sysv/linux/hppa/libc.abilist
@@ -1869,6 +1869,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/i386/libc.abilist b/sysdeps/unix/sysv/linux/i386/libc.abilist
index cffdf251d6..0d7aca5d39 100644
--- a/sysdeps/unix/sysv/linux/i386/libc.abilist
+++ b/sysdeps/unix/sysv/linux/i386/libc.abilist
@@ -2034,6 +2034,7 @@  GLIBC_2.26 strtof128_l F
 GLIBC_2.26 wcstof128 F
 GLIBC_2.26 wcstof128_l F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/ia64/libc.abilist b/sysdeps/unix/sysv/linux/ia64/libc.abilist
index 3292510a55..a7a740aa1a 100644
--- a/sysdeps/unix/sysv/linux/ia64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/ia64/libc.abilist
@@ -1898,6 +1898,7 @@  GLIBC_2.26 strtof128_l F
 GLIBC_2.26 wcstof128 F
 GLIBC_2.26 wcstof128_l F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/kernel-features.h b/sysdeps/unix/sysv/linux/kernel-features.h
index 2e1fe6597a..ac2d5412e7 100644
--- a/sysdeps/unix/sysv/linux/kernel-features.h
+++ b/sysdeps/unix/sysv/linux/kernel-features.h
@@ -107,3 +107,8 @@ 
 #if __LINUX_KERNEL_VERSION >= 0x031300
 # define __ASSUME_EXECVEAT	1
 #endif
+
+/* Support for the copy_file_range syscall was added in 4.5.  */
+#if __LINUX_KERNEL_VERSION >= 0x040500
+# define __ASSUME_COPY_FILE_RANGE 1
+#endif
diff --git a/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist b/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
index 636bbdd1a7..06b195f548 100644
--- a/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
+++ b/sysdeps/unix/sysv/linux/m68k/coldfire/libc.abilist
@@ -106,6 +106,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist b/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
index 6952863f86..4bc674f5fb 100644
--- a/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
+++ b/sysdeps/unix/sysv/linux/m68k/m680x0/libc.abilist
@@ -1983,6 +1983,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/microblaze/libc.abilist b/sysdeps/unix/sysv/linux/microblaze/libc.abilist
index ac5b56abab..ea4c597c56 100644
--- a/sysdeps/unix/sysv/linux/microblaze/libc.abilist
+++ b/sysdeps/unix/sysv/linux/microblaze/libc.abilist
@@ -2104,6 +2104,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
index bb0958e842..c0cb2ea16d 100644
--- a/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips32/fpu/libc.abilist
@@ -1958,6 +1958,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
index 9104eb4d6d..1afb7b7544 100644
--- a/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips32/nofpu/libc.abilist
@@ -1956,6 +1956,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
index 58a5d5e141..b093d879ab 100644
--- a/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips64/n32/libc.abilist
@@ -1954,6 +1954,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist b/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
index 2efac14a7d..11c7fc3242 100644
--- a/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/mips/mips64/n64/libc.abilist
@@ -1949,6 +1949,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/nios2/libc.abilist b/sysdeps/unix/sysv/linux/nios2/libc.abilist
index 9ef29e4e98..94933fd4dc 100644
--- a/sysdeps/unix/sysv/linux/nios2/libc.abilist
+++ b/sysdeps/unix/sysv/linux/nios2/libc.abilist
@@ -2145,6 +2145,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
index 60c024096f..7bb7b67dd1 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/fpu/libc.abilist
@@ -1987,6 +1987,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
index 327933c973..f58fe26fe2 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc32/nofpu/libc.abilist
@@ -1992,6 +1992,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/libc-le.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc64/libc-le.abilist
index b04c31bc10..9ab82af581 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/libc-le.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/libc-le.abilist
@@ -2199,6 +2199,7 @@  GLIBC_2.26 strtof128_l F
 GLIBC_2.26 wcstof128 F
 GLIBC_2.26 wcstof128_l F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/libc.abilist b/sysdeps/unix/sysv/linux/powerpc/powerpc64/libc.abilist
index e0645e9e25..73f3dac107 100644
--- a/sysdeps/unix/sysv/linux/powerpc/powerpc64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/powerpc/powerpc64/libc.abilist
@@ -106,6 +106,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist b/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
index ef434c61a7..69747dc09e 100644
--- a/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/s390/s390-32/libc.abilist
@@ -1987,6 +1987,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist b/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
index 4114a4ce57..7121b9d7d4 100644
--- a/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/s390/s390-64/libc.abilist
@@ -1888,6 +1888,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/sh/libc.abilist b/sysdeps/unix/sysv/linux/sh/libc.abilist
index f4478b0cc5..c18d88f713 100644
--- a/sysdeps/unix/sysv/linux/sh/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sh/libc.abilist
@@ -1873,6 +1873,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist b/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
index 136a57fc0e..04e5483280 100644
--- a/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sparc/sparc32/libc.abilist
@@ -1980,6 +1980,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist b/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
index 9ad0790829..f68b068c25 100644
--- a/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/sparc/sparc64/libc.abilist
@@ -1917,6 +1917,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/tile/tilegx/tilegx32/libc.abilist b/sysdeps/unix/sysv/linux/tile/tilegx/tilegx32/libc.abilist
index d4f2094027..019814ed5b 100644
--- a/sysdeps/unix/sysv/linux/tile/tilegx/tilegx32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/tile/tilegx/tilegx32/libc.abilist
@@ -2111,6 +2111,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/tile/tilegx/tilegx64/libc.abilist b/sysdeps/unix/sysv/linux/tile/tilegx/tilegx64/libc.abilist
index 4916dbabb5..3eac5368db 100644
--- a/sysdeps/unix/sysv/linux/tile/tilegx/tilegx64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/tile/tilegx/tilegx64/libc.abilist
@@ -2111,6 +2111,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/tile/tilepro/libc.abilist b/sysdeps/unix/sysv/linux/tile/tilepro/libc.abilist
index d4f2094027..019814ed5b 100644
--- a/sysdeps/unix/sysv/linux/tile/tilepro/libc.abilist
+++ b/sysdeps/unix/sysv/linux/tile/tilepro/libc.abilist
@@ -2111,6 +2111,7 @@  GLIBC_2.26 pwritev2 F
 GLIBC_2.26 pwritev64v2 F
 GLIBC_2.26 reallocarray F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
index 1ea74f9e8c..472d0fa013 100644
--- a/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
+++ b/sysdeps/unix/sysv/linux/x86_64/64/libc.abilist
@@ -1875,6 +1875,7 @@  GLIBC_2.26 strtof128_l F
 GLIBC_2.26 wcstof128 F
 GLIBC_2.26 wcstof128_l F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F
diff --git a/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist b/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
index 1d3d598618..27b027aecc 100644
--- a/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
+++ b/sysdeps/unix/sysv/linux/x86_64/x32/libc.abilist
@@ -2118,6 +2118,7 @@  GLIBC_2.26 strtof128_l F
 GLIBC_2.26 wcstof128 F
 GLIBC_2.26 wcstof128_l F
 GLIBC_2.27 GLIBC_2.27 A
+GLIBC_2.27 copy_file_range F
 GLIBC_2.27 glob F
 GLIBC_2.27 glob64 F
 GLIBC_2.27 memfd_create F