Skip to content

Commit 63d5003

Browse files
committed
[libc++] use copy_file_range for fs::copy_file
opportunistically use copy_file_range (Linux, FreeBSD) where possible. This allows for fast copies via reflinks, and server side copies for network filesystems. Fall back to sendfile if not supported. Signed-off-by: Jannik Glückert <[email protected]>
1 parent 5f02558 commit 63d5003

File tree

2 files changed

+75
-1
lines changed

2 files changed

+75
-1
lines changed

libcxx/src/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ if (APPLE AND LLVM_USE_SANITIZER)
173173
endif()
174174
endif()
175175

176+
include(CheckCXXSymbolExists)
177+
check_cxx_symbol_exists("copy_file_range" "unistd.h" LIBCXX_USE_COPY_FILE_RANGE)
178+
if(LIBCXX_USE_COPY_FILE_RANGE)
179+
list(APPEND LIBCXX_COMPILE_FLAGS "-D_LIBCPP_FILESYSTEM_USE_COPY_FILE_RANGE")
180+
endif()
181+
176182
split_list(LIBCXX_COMPILE_FLAGS)
177183
split_list(LIBCXX_LINK_FLAGS)
178184

libcxx/src/filesystem/operations.cpp

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
# include <dirent.h>
3333
# include <sys/stat.h>
3434
# include <sys/statvfs.h>
35+
# include <sys/types.h>
3536
# include <unistd.h>
3637
#endif
3738
#include <fcntl.h> /* values for fchmodat */
@@ -178,8 +179,35 @@ void __copy(const path& from, const path& to, copy_options options, error_code*
178179
namespace detail {
179180
namespace {
180181

182+
#if defined(_LIBCPP_FILESYSTEM_USE_COPY_FILE_RANGE)
183+
bool copy_file_impl_copy_file_range(FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
184+
size_t count = read_fd.get_stat().st_size;
185+
// a zero-length file is either empty, or not copyable by this syscall
186+
// return early to avoid the syscall cost
187+
if (count == 0) {
188+
ec = {EINVAL, generic_category()};
189+
return false;
190+
}
191+
// do not modify the fd positions as copy_file_impl_sendfile may be called after a partial copy
192+
off_t off_in = 0;
193+
off_t off_out = 0;
194+
do {
195+
ssize_t res;
196+
197+
if ((res = ::copy_file_range(read_fd.fd, &off_in, write_fd.fd, &off_out, count, 0)) == -1) {
198+
ec = capture_errno();
199+
return false;
200+
}
201+
count -= res;
202+
} while (count > 0);
203+
204+
ec.clear();
205+
206+
return true;
207+
}
208+
#endif
181209
#if defined(_LIBCPP_FILESYSTEM_USE_SENDFILE)
182-
bool copy_file_impl(FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
210+
bool copy_file_impl_sendfile(FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
183211
size_t count = read_fd.get_stat().st_size;
184212
do {
185213
ssize_t res;
@@ -194,6 +222,46 @@ bool copy_file_impl(FileDescriptor& read_fd, FileDescriptor& write_fd, error_cod
194222

195223
return true;
196224
}
225+
#endif
226+
227+
#if defined(_LIBCPP_FILESYSTEM_USE_COPY_FILE_RANGE) || defined(_LIBCPP_FILESYSTEM_USE_SENDFILE)
228+
bool copy_file_impl(FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
229+
# if defined(_LIBCPP_FILESYSTEM_USE_COPY_FILE_RANGE)
230+
if (copy_file_impl_copy_file_range(read_fd, write_fd, ec)) {
231+
return true;
232+
}
233+
// EINVAL: src and dst are the same file (this is not cheaply
234+
// detectable from userspace)
235+
// EINVAL: copy_file_range is unsupported for this file type by the
236+
// underlying filesystem
237+
// ENOTSUP: undocumented, can arise with old kernels and NFS
238+
// EOPNOTSUPP: filesystem does not implement copy_file_range
239+
// ETXTBSY: src or dst is an active swapfile (nonsensical, but allowed
240+
// with normal copying)
241+
// EXDEV: src and dst are on different filesystems that do not support
242+
// cross-fs copy_file_range
243+
// ENOENT: undocumented, can arise with CIFS
244+
// ENOSYS: unsupported by kernel or blocked by seccomp
245+
if (ec.value() != EINVAL && ec.value() != ENOTSUP && ec.value() != EOPNOTSUPP && ec.value() != ETXTBSY &&
246+
ec.value() != EXDEV && ec.value() != ENOENT && ec.value() != ENOSYS) {
247+
return false;
248+
}
249+
ec.clear();
250+
# endif
251+
252+
# if defined(_LIBCPP_FILESYSTEM_USE_SENDFILE)
253+
if (copy_file_impl_sendfile(read_fd, write_fd, ec)) {
254+
return true;
255+
}
256+
// EINVAL: unsupported file type
257+
if (ec.value() != EINVAL) {
258+
return false;
259+
}
260+
ec.clear();
261+
# endif
262+
ec = {EINVAL, generic_category()};
263+
return false;
264+
}
197265
#elif defined(_LIBCPP_FILESYSTEM_USE_COPYFILE)
198266
bool copy_file_impl(FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
199267
struct CopyFileState {

0 commit comments

Comments
 (0)