libstdc++: Use copy_file_range for filesystem::copy_file

copy_file_range is a recent-ish syscall for copying files. It is similar
to sendfile but allows filesystem-specific optimizations. Common are:
Reflinks: BTRFS, XFS, ZFS (does not implement the syscall yet)
Server-side copy: NFS, SMB, Ceph

If copy_file_range is not available for the given files, fall back to
sendfile / userspace copy.

libstdc++-v3/ChangeLog:

	* acinclude.m4 (_GLIBCXX_USE_COPY_FILE_RANGE): Define.
	* config.h.in: Regenerate.
	* configure: Regenerate.
	* src/filesystem/ops-common.h (copy_file_copy_file_range):
	Define new function.
	(do_copy_file): Use it.

Signed-off-by: Jannik Glückert <jannik.glueckert@gmail.com>
This commit is contained in:
Jannik Glückert 2023-03-08 19:37:43 +01:00 committed by Jonathan Wakely
parent f80a8b4229
commit d87caacf8e
4 changed files with 141 additions and 0 deletions

View File

@ -4954,6 +4954,7 @@ dnl _GLIBCXX_USE_UTIMENSAT
dnl _GLIBCXX_USE_ST_MTIM
dnl _GLIBCXX_USE_FCHMOD
dnl _GLIBCXX_USE_FCHMODAT
dnl _GLIBCXX_USE_COPY_FILE_RANGE
dnl _GLIBCXX_USE_SENDFILE
dnl HAVE_LINK
dnl HAVE_LSEEK
@ -5152,6 +5153,25 @@ dnl
if test $glibcxx_cv_truncate = yes; then
AC_DEFINE(HAVE_TRUNCATE, 1, [Define if truncate is available in <unistd.h>.])
fi
dnl
AC_CACHE_CHECK([for copy_file_range that can copy files],
glibcxx_cv_copy_file_range, [dnl
case "${target_os}" in
linux*)
GCC_TRY_COMPILE_OR_LINK(
[#include <unistd.h>],
[copy_file_range(1, nullptr, 2, nullptr, 1, 0);],
[glibcxx_cv_copy_file_range=yes],
[glibcxx_cv_copy_file_range=no])
;;
*)
glibcxx_cv_copy_file_range=no
;;
esac
])
if test $glibcxx_cv_copy_file_range = yes; then
AC_DEFINE(_GLIBCXX_USE_COPY_FILE_RANGE, 1, [Define if copy_file_range is available in <unistd.h>.])
fi
dnl
AC_CACHE_CHECK([for sendfile that can copy files],
glibcxx_cv_sendfile, [dnl

View File

@ -971,6 +971,9 @@
/* Defined if clock_gettime has realtime clock support. */
#undef _GLIBCXX_USE_CLOCK_REALTIME
/* Define if copy_file_range is available in <unistd.h>. */
#undef _GLIBCXX_USE_COPY_FILE_RANGE
/* Define if ISO/IEC TR 24733 decimal floating point types are supported on
this host. */
#undef _GLIBCXX_USE_DECIMAL_FLOAT

View File

@ -71279,6 +71279,68 @@ $as_echo "$glibcxx_cv_truncate" >&6; }
$as_echo "#define HAVE_TRUNCATE 1" >>confdefs.h
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for copy_file_range that can copy files" >&5
$as_echo_n "checking for copy_file_range that can copy files... " >&6; }
if ${glibcxx_cv_copy_file_range+:} false; then :
$as_echo_n "(cached) " >&6
else
case "${target_os}" in
linux*)
if test x$gcc_no_link = xyes; then
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include <unistd.h>
int
main ()
{
copy_file_range(1, nullptr, 2, nullptr, 1, 0);
;
return 0;
}
_ACEOF
if ac_fn_cxx_try_compile "$LINENO"; then :
glibcxx_cv_copy_file_range=yes
else
glibcxx_cv_copy_file_range=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
else
if test x$gcc_no_link = xyes; then
as_fn_error $? "Link tests are not allowed after GCC_NO_EXECUTABLES." "$LINENO" 5
fi
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include <unistd.h>
int
main ()
{
copy_file_range(1, nullptr, 2, nullptr, 1, 0);
;
return 0;
}
_ACEOF
if ac_fn_cxx_try_link "$LINENO"; then :
glibcxx_cv_copy_file_range=yes
else
glibcxx_cv_copy_file_range=no
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
fi
;;
*)
glibcxx_cv_copy_file_range=no
;;
esac
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $glibcxx_cv_copy_file_range" >&5
$as_echo "$glibcxx_cv_copy_file_range" >&6; }
if test $glibcxx_cv_copy_file_range = yes; then
$as_echo "#define _GLIBCXX_USE_COPY_FILE_RANGE 1" >>confdefs.h
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for sendfile that can copy files" >&5
$as_echo_n "checking for sendfile that can copy files... " >&6; }

View File

@ -49,6 +49,9 @@
#ifdef NEED_DO_COPY_FILE
# include <filesystem>
# include <ext/stdio_filebuf.h>
# ifdef _GLIBCXX_USE_COPY_FILE_RANGE
# include <unistd.h> // copy_file_range
# endif
# ifdef _GLIBCXX_USE_SENDFILE
# include <sys/sendfile.h> // sendfile
# include <unistd.h> // lseek
@ -359,6 +362,32 @@ _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM
}
#ifdef NEED_DO_COPY_FILE
#ifdef _GLIBCXX_USE_COPY_FILE_RANGE
bool
copy_file_copy_file_range(int fd_in, int fd_out, size_t length) noexcept
{
// a zero-length file is either empty, or not copyable by this syscall
// return early to avoid the syscall cost
if (length == 0)
{
errno = EINVAL;
return false;
}
size_t bytes_left = length;
off64_t off_in = 0, off_out = 0;
ssize_t bytes_copied;
do
{
bytes_copied = ::copy_file_range(fd_in, &off_in, fd_out, &off_out,
bytes_left, 0);
bytes_left -= bytes_copied;
}
while (bytes_left > 0 && bytes_copied > 0);
if (bytes_copied < 0)
return false;
return true;
}
#endif
#if defined _GLIBCXX_USE_SENDFILE && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS
bool
copy_file_sendfile(int fd_in, int fd_out, size_t length) noexcept
@ -529,6 +558,33 @@ _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM
bool has_copied = false;
#ifdef _GLIBCXX_USE_COPY_FILE_RANGE
if (!has_copied)
has_copied = copy_file_copy_file_range(in.fd, out.fd, from_st->st_size);
if (!has_copied)
{
// EINVAL: src and dst are the same file (this is not cheaply
// detectable from userspace)
// EINVAL: copy_file_range is unsupported for this file type by the
// underlying filesystem
// ENOTSUP: undocumented, can arise with old kernels and NFS
// EOPNOTSUPP: filesystem does not implement copy_file_range
// ETXTBSY: src or dst is an active swapfile (nonsensical, but allowed
// with normal copying)
// EXDEV: src and dst are on different filesystems that do not support
// cross-fs copy_file_range
// ENOENT: undocumented, can arise with CIFS
// ENOSYS: unsupported by kernel or blocked by seccomp
if (errno != EINVAL && errno != ENOTSUP && errno != EOPNOTSUPP
&& errno != ETXTBSY && errno != EXDEV && errno != ENOENT
&& errno != ENOSYS)
{
ec.assign(errno, std::generic_category());
return false;
}
}
#endif
#if defined _GLIBCXX_USE_SENDFILE && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS
if (!has_copied)
has_copied = copy_file_sendfile(in.fd, out.fd, from_st->st_size);