Add copy_file_range(2) syscall stub to bionic.

Test: treehugger
Bug: https://buganizer.corp.google.com/issues/227784687
Change-Id: I543306cd2234189401bf7c9d80d405eeb6e4d41d
diff --git a/libc/SYSCALLS.TXT b/libc/SYSCALLS.TXT
index 1435e79..45b2a1b 100644
--- a/libc/SYSCALLS.TXT
+++ b/libc/SYSCALLS.TXT
@@ -108,6 +108,7 @@
 
 int         __close:close(int)  all
 int         close_range(unsigned int, unsigned int, int) all
+ssize_t     copy_file_range(int, off64_t*, int, off64_t*, size_t, unsigned int) all
 pid_t       __getpid:getpid()  all
 int memfd_create(const char*, unsigned) all
 int         munmap(void*, size_t)  all
diff --git a/libc/include/unistd.h b/libc/include/unistd.h
index f142525..b5694b8 100644
--- a/libc/include/unistd.h
+++ b/libc/include/unistd.h
@@ -313,6 +313,17 @@
 int getdomainname(char* __buf, size_t __buf_size) __INTRODUCED_IN(26);
 int setdomainname(const char* __name, size_t __n) __INTRODUCED_IN(26);
 
+/**
+ * [copy_file_range(2)](https://man7.org/linux/man-pages/man2/copy_file_range.2.html) copies
+ * a range of data from one file descriptor to another.
+ *
+ * Returns the number of bytes copied on success, and returns -1 and sets  errno
+ * on failure.
+ *
+ * Available since API level 34.
+ */
+ssize_t copy_file_range(int __fd_in, off64_t* __off_in, int __fd_out, off64_t* __off_out, size_t __length, unsigned int __flags) __INTRODUCED_IN(34);
+
 #if __ANDROID_API__ >= 28
 void swab(const void* __src, void* __dst, ssize_t __byte_count) __INTRODUCED_IN(28);
 #endif
diff --git a/libc/libc.map.txt b/libc/libc.map.txt
index e64fe00..79f331a 100644
--- a/libc/libc.map.txt
+++ b/libc/libc.map.txt
@@ -1579,6 +1579,7 @@
 LIBC_U { # introduced=UpsideDownCake
   global:
     close_range;
+    copy_file_range;
 } LIBC_T;
 
 LIBC_PRIVATE {
diff --git a/tests/unistd_test.cpp b/tests/unistd_test.cpp
index 293b45a..49fec12 100644
--- a/tests/unistd_test.cpp
+++ b/tests/unistd_test.cpp
@@ -1668,3 +1668,19 @@
   }
 #endif  // __GLIBC__
 }
+
+TEST(UNISTD_TEST, copy_file_range) {
+#if defined(__GLIBC__)
+  GTEST_SKIP() << "glibc too old";
+#else   // __GLIBC__
+  TemporaryFile tf;
+  ASSERT_TRUE(android::base::WriteStringToFd("hello world", tf.fd));
+  ASSERT_EQ(0, lseek(tf.fd, SEEK_SET, 0));
+  TemporaryFile tf2;
+  ASSERT_EQ(11, copy_file_range(tf.fd, NULL, tf2.fd, NULL, 11, 0));
+  ASSERT_EQ(0, lseek(tf2.fd, SEEK_SET, 0));
+  std::string content;
+  ASSERT_TRUE(android::base::ReadFdToString(tf2.fd, &content));
+  ASSERT_EQ("hello world", content);
+#endif  // __GLIBC__
+}
\ No newline at end of file