Skip to content

Commit 2e871a9

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 f3c675f commit 2e871a9

File tree

1 file changed

+74
-1
lines changed

1 file changed

+74
-1
lines changed

libcxx/src/filesystem/operations.cpp

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,16 @@
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 */
3839
#include <time.h>
3940

41+
// since Linux 4.5 and FreeBSD 13
42+
#if defined(__linux__) || defined(__FreeBSD__)
43+
# define _LIBCPP_FILESYSTEM_USE_COPY_FILE_RANGE
44+
#endif
4045
#if __has_include(<sys/sendfile.h>)
4146
# include <sys/sendfile.h>
4247
# define _LIBCPP_FILESYSTEM_USE_SENDFILE
@@ -178,8 +183,36 @@ void __copy(const path& from, const path& to, copy_options options, error_code*
178183
namespace detail {
179184
namespace {
180185

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

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

0 commit comments

Comments
 (0)