[v2,1/2] libstdc++: use copy_file_range, improve sendfile in filesystem::copy_file
Checks
Commit Message
This iteration improves error handling for copy_file_range,
particularly around undocumented error codes in earlier kernel
versions.
Additionally this fixes the userspace copy fallback to handle
zero-length files such as in
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108178.
Lastly, the case "src gets resized during the copy loop" is now
considered and will return true once the loop hits EOF (this is the
only situation, aside from a zero-length src, where sendfile and
copy_file_range return 0).
Best
Jannik
Comments
On Wed, 15 Mar 2023 at 19:30, Jannik Glückert via Libstdc++ <
libstdc++@gcc.gnu.org> wrote:
> This iteration improves error handling for copy_file_range,
> particularly around undocumented error codes in earlier kernel
> versions.
> Additionally this fixes the userspace copy fallback to handle
> zero-length files such as in
> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108178.
>
> Lastly, the case "src gets resized during the copy loop" is now
> considered and will return true once the loop hits EOF (this is the
> only situation, aside from a zero-length src, where sendfile and
> copy_file_range return 0).
>
I've applied this patch (with some whitespace fixes) and started testing
it. I'm seeing some regressions:
FAIL: experimental/filesystem/operations/copy.cc execution test
FAIL: experimental/filesystem/operations/copy_file.cc execution test
The failures in the log look like:
terminate called after throwing an instance of
'std::experimental::filesystem::v1::__cxx11::filesystem_error'
what(): filesystem error: cannot copy: Input/output error
[filesystem-test.copy.2900321341.ySWT77]
[filesystem-test.copy.2900321342.vjeAar]
FAIL: experimental/filesystem/operations/copy.cc execution test
On Mon, 20 Mar 2023 at 15:16, Jonathan Wakely wrote:
>
>
> On Wed, 15 Mar 2023 at 19:30, Jannik Glückert via Libstdc++ <
> libstdc++@gcc.gnu.org> wrote:
>
>> This iteration improves error handling for copy_file_range,
>> particularly around undocumented error codes in earlier kernel
>> versions.
>> Additionally this fixes the userspace copy fallback to handle
>> zero-length files such as in
>> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108178.
>>
>> Lastly, the case "src gets resized during the copy loop" is now
>> considered and will return true once the loop hits EOF (this is the
>> only situation, aside from a zero-length src, where sendfile and
>> copy_file_range return 0).
>>
>
> I've applied this patch (with some whitespace fixes) and started testing
> it. I'm seeing some regressions:
>
> FAIL: experimental/filesystem/operations/copy.cc execution test
> FAIL: experimental/filesystem/operations/copy_file.cc execution test
>
> The failures in the log look like:
>
> terminate called after throwing an instance of
> 'std::experimental::filesystem::v1::__cxx11::filesystem_error'
> what(): filesystem error: cannot copy: Input/output error
> [filesystem-test.copy.2900321341.ySWT77]
> [filesystem-test.copy.2900321342.vjeAar]
> FAIL: experimental/filesystem/operations/copy.cc execution test
>
Not just the Filesystem TS versions of those tests, but the std::filesystem
ones too:
FAIL: 27_io/filesystem/operations/copy.cc execution test
FAIL: 27_io/filesystem/operations/copy_file.cc execution test
On 06/03/23 20:52 +0100, Jannik Glückert wrote:
>we were previously only using sendfile for files smaller than 2GB, as
>sendfile needs to be called repeatedly for files bigger than that.
>
>some quick numbers, copying a 16GB file, average of 10 repetitions:
> old:
> real: 13.4s
> user: 0.14s
> sys : 7.43s
> new:
> real: 8.90s
> user: 0.00s
> sys : 3.68s
>
>Additionally, this fixes
>https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108178
>
>libstdc++-v3/ChangeLog:
>
> * acinclude.m4 (_GLIBCXX_HAVE_LSEEK): define
> * config.h.in: Regenerate.
> * configure: Regenerate.
> * src/filesystem/ops-common.h: enable sendfile for files
> >2GB in std::filesystem::copy_file, skip zero-length files
>
>Signed-off-by: Jannik Glückert <jannik.glueckert@gmail.com>
>---
> libstdc++-v3/acinclude.m4 | 51 +++++----
> libstdc++-v3/config.h.in | 3 +
> libstdc++-v3/configure | 127 ++++++++++++++++-------
> libstdc++-v3/src/filesystem/ops-common.h | 86 ++++++++-------
> 4 files changed, 175 insertions(+), 92 deletions(-)
>
>diff --git a/libstdc++-v3/acinclude.m4 b/libstdc++-v3/acinclude.m4
>index 5136c0571e8..85a09a5a869 100644
>--- a/libstdc++-v3/acinclude.m4
>+++ b/libstdc++-v3/acinclude.m4
>@@ -4583,6 +4583,7 @@ dnl _GLIBCXX_USE_FCHMOD
> dnl _GLIBCXX_USE_FCHMODAT
> dnl _GLIBCXX_USE_SENDFILE
> dnl HAVE_LINK
>+dnl HAVE_LSEEK
> dnl HAVE_READLINK
> dnl HAVE_SYMLINK
> dnl
>@@ -4718,25 +4719,6 @@ dnl
> if test $glibcxx_cv_fchmodat = yes; then
> AC_DEFINE(_GLIBCXX_USE_FCHMODAT, 1, [Define if fchmodat is available in <sys/stat.h>.])
> fi
>-dnl
>- AC_CACHE_CHECK([for sendfile that can copy files],
>- glibcxx_cv_sendfile, [dnl
>- case "${target_os}" in
>- gnu* | linux* | solaris* | uclinux*)
>- GCC_TRY_COMPILE_OR_LINK(
>- [#include <sys/sendfile.h>],
>- [sendfile(1, 2, (off_t*)0, sizeof 1);],
>- [glibcxx_cv_sendfile=yes],
>- [glibcxx_cv_sendfile=no])
>- ;;
>- *)
>- glibcxx_cv_sendfile=no
>- ;;
>- esac
>- ])
>- if test $glibcxx_cv_sendfile = yes; then
>- AC_DEFINE(_GLIBCXX_USE_SENDFILE, 1, [Define if sendfile is available in <sys/sendfile.h>.])
>- fi
> dnl
> AC_CACHE_CHECK([for link],
> glibcxx_cv_link, [dnl
>@@ -4749,6 +4731,18 @@ dnl
> if test $glibcxx_cv_link = yes; then
> AC_DEFINE(HAVE_LINK, 1, [Define if link is available in <unistd.h>.])
> fi
>+dnl
>+ AC_CACHE_CHECK([for lseek],
>+ glibcxx_cv_lseek, [dnl
>+ GCC_TRY_COMPILE_OR_LINK(
>+ [#include <unistd.h>],
>+ [lseek(1, 0, SEEK_SET);],
>+ [glibcxx_cv_lseek=yes],
>+ [glibcxx_cv_lseek=no])
>+ ])
>+ if test $glibcxx_cv_lseek = yes; then
>+ AC_DEFINE(HAVE_LSEEK, 1, [Define if lseek is available in <unistd.h>.])
>+ fi
> dnl
> AC_CACHE_CHECK([for readlink],
> glibcxx_cv_readlink, [dnl
>@@ -4785,6 +4779,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 sendfile that can copy files],
>+ glibcxx_cv_sendfile, [dnl
>+ case "${target_os}" in
>+ gnu* | linux* | solaris* | uclinux*)
>+ GCC_TRY_COMPILE_OR_LINK(
>+ [#include <sys/sendfile.h>],
>+ [sendfile(1, 2, (off_t*)0, sizeof 1);],
>+ [glibcxx_cv_sendfile=yes],
>+ [glibcxx_cv_sendfile=no])
>+ ;;
>+ *)
>+ glibcxx_cv_sendfile=no
>+ ;;
>+ esac
>+ ])
>+ if test $glibcxx_cv_sendfile = yes && test $glibcxx_cv_lseek = yes; then
>+ AC_DEFINE(_GLIBCXX_USE_SENDFILE, 1, [Define if sendfile is available in <sys/sendfile.h>.])
>+ fi
> dnl
> AC_CACHE_CHECK([for fdopendir],
> glibcxx_cv_fdopendir, [dnl
>diff --git a/libstdc++-v3/src/filesystem/ops-common.h b/libstdc++-v3/src/filesystem/ops-common.h
>index abbfca43e5c..9e1b1d41dc5 100644
>--- a/libstdc++-v3/src/filesystem/ops-common.h
>+++ b/libstdc++-v3/src/filesystem/ops-common.h
>@@ -51,6 +51,7 @@
> # include <ext/stdio_filebuf.h>
> # ifdef _GLIBCXX_USE_SENDFILE
> # include <sys/sendfile.h> // sendfile
>+# include <unistd.h> // lseek
> # endif
> #endif
>
>@@ -358,6 +359,32 @@ _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM
> }
>
> #ifdef NEED_DO_COPY_FILE
>+#if defined _GLIBCXX_USE_SENDFILE && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS
>+ bool
>+ copy_file_sendfile(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;
>+ off_t offset = 0;
>+ ssize_t bytes_copied;
>+ do {
>+ bytes_copied = ::sendfile(fd_out, fd_in, &offset, bytes_left);
>+ bytes_left -= bytes_copied;
>+ } while (bytes_left > 0 && bytes_copied > 0);
>+ if (bytes_copied < 0)
>+ {
>+ ::lseek(fd_out, 0, SEEK_SET);
>+ return false;
>+ }
>+ return true;
>+ }
>+#endif
> bool
> do_copy_file(const char_type* from, const char_type* to,
> std::filesystem::copy_options_existing_file options,
>@@ -498,28 +525,30 @@ _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM
> return false;
> }
>
>- size_t count = from_st->st_size;
>+ bool has_copied = false;
>+
> #if defined _GLIBCXX_USE_SENDFILE && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS
>- off_t offset = 0;
>- ssize_t n = ::sendfile(out.fd, in.fd, &offset, count);
>- if (n < 0 && errno != ENOSYS && errno != EINVAL)
>+ if (!has_copied)
>+ has_copied = copy_file_sendfile(in.fd, out.fd, from_st->st_size);
>+ if (!has_copied)
> {
>- ec.assign(errno, std::generic_category());
>- return false;
>+ if (errno != ENOSYS && errno != EINVAL)
Indentation is wrong here.
>+ {
>+ ec.assign(errno, std::generic_category());
>+ return false;
>+ }
> }
>- if ((size_t)n == count)
>+#endif
>+
>+ if (has_copied)
> {
>- if (!out.close() || !in.close())
>- {
>- ec.assign(errno, std::generic_category());
>- return false;
>- }
>- ec.clear();
>- return true;
>+ if (!out.close() || !in.close())
>+ {
>+ ec.assign(errno, std::generic_category());
Indentation is wrong here. Tabstop should be 8, so this should have
one tab followed by four spaces.
>+ return false;
>+ }
You need to ec.clear() before returning true here.
>+ return true;
> }
>- else if (n > 0)
>- count -= n;
>-#endif // _GLIBCXX_USE_SENDFILE
>
> using std::ios;
> __gnu_cxx::stdio_filebuf<char> sbin(in.fd, ios::in|ios::binary);
>@@ -530,29 +559,12 @@ _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM
> if (sbout.is_open())
> out.fd = -1;
>
>-#ifdef _GLIBCXX_USE_SENDFILE
>- if (n != 0)
>+ if (!(std::ostream(&sbout) << &sbin))
As discussed on IRC, removing the `if (count && ...)` part of this
condition breaks copying empty files, because the semantics of
ostream::operator<<(streambuf*) are:
"If the function inserts no characters, it calls setstate(failbit)"
So let's keep the original behaviour here, and deal with fixing PR
108178 in a separate patch.
> {
>- if (n < 0)
>- n = 0;
>-
>- const auto p1 = sbin.pubseekoff(n, ios::beg, ios::in);
>- const auto p2 = sbout.pubseekoff(n, ios::beg, ios::out);
>-
>- const std::streampos errpos(std::streamoff(-1));
>- if (p1 == errpos || p2 == errpos)
>- {
>- ec = std::make_error_code(std::errc::io_error);
>- return false;
>- }
>+ ec = std::make_error_code(std::errc::io_error);
>+ return false;
Indentation is wrong here.
> }
>-#endif
>
>- if (count && !(std::ostream(&sbout) << &sbin))
>- {
>- ec = std::make_error_code(std::errc::io_error);
>- return false;
>- }
> if (!sbout.close() || !sbin.close())
> {
> ec.assign(errno, std::generic_category());
>--
>2.39.2
On 20/03/23 22:27 +0000, Jonathan Wakely wrote:
>On 06/03/23 20:52 +0100, Jannik Glückert wrote:
>>we were previously only using sendfile for files smaller than 2GB, as
>>sendfile needs to be called repeatedly for files bigger than that.
>>
>>some quick numbers, copying a 16GB file, average of 10 repetitions:
>> old:
>> real: 13.4s
>> user: 0.14s
>> sys : 7.43s
>> new:
>> real: 8.90s
>> user: 0.00s
>> sys : 3.68s
>>
>>Additionally, this fixes
>>https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108178
>>
>>libstdc++-v3/ChangeLog:
>>
>> * acinclude.m4 (_GLIBCXX_HAVE_LSEEK): define
>> * config.h.in: Regenerate.
>> * configure: Regenerate.
>> * src/filesystem/ops-common.h: enable sendfile for files
>> >2GB in std::filesystem::copy_file, skip zero-length files
Also, the ChangeLog entry needs to be indented with tabs, name the
changed functions, and should be complete sentences, e.g.
* acinclude.m4 (_GLIBCXX_HAVE_LSEEK): Define.
* config.h.in: Regenerate.
* configure: Regenerate.
* src/filesystem/ops-common.h (copy_file_sendfile): Define new
function for sendfile logic. Loop to support large files. Skip
zero-length files.
(do_copy_file): Use it.
On Mon, 20 Mar 2023 at 22:30, Jonathan Wakely via Libstdc++ <
libstdc++@gcc.gnu.org> wrote:
> On 20/03/23 22:27 +0000, Jonathan Wakely wrote:
> >On 06/03/23 20:52 +0100, Jannik Glückert wrote:
> >>we were previously only using sendfile for files smaller than 2GB, as
> >>sendfile needs to be called repeatedly for files bigger than that.
> >>
> >>some quick numbers, copying a 16GB file, average of 10 repetitions:
> >> old:
> >> real: 13.4s
> >> user: 0.14s
> >> sys : 7.43s
> >> new:
> >> real: 8.90s
> >> user: 0.00s
> >> sys : 3.68s
> >>
> >>Additionally, this fixes
> >>https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108178
> >>
> >>libstdc++-v3/ChangeLog:
> >>
> >> * acinclude.m4 (_GLIBCXX_HAVE_LSEEK): define
> >> * config.h.in: Regenerate.
> >> * configure: Regenerate.
> >> * src/filesystem/ops-common.h: enable sendfile for files
> >> >2GB in std::filesystem::copy_file, skip zero-length files
>
> Also, the ChangeLog entry needs to be indented with tabs, name the
> changed functions, and should be complete sentences, e.g.
>
> * acinclude.m4 (_GLIBCXX_HAVE_LSEEK): Define.
> * config.h.in: Regenerate.
> * configure: Regenerate.
> * src/filesystem/ops-common.h (copy_file_sendfile): Define new
> function for sendfile logic. Loop to support large files. Skip
> zero-length files.
> (do_copy_file): Use it.
>
>
Here's what I plan to commit in a few weeks when GCC 14 Stage 1 opens.
commit c177825f952bb353cdf412f46f45539b8992abe1
Author: Jannik Glückert <jannik.glueckert@gmail.com>
Date: Mon Mar 6 19:52:08 2023
libstdc++: Also use sendfile for big files
We were previously only using sendfile for files smaller than 2GB, as
sendfile needs to be called repeatedly for files bigger than that.
Some quick numbers, copying a 16GB file, average of 10 repetitions:
old:
real: 13.4s
user: 0.14s
sys : 7.43s
new:
real: 8.90s
user: 0.00s
sys : 3.68s
libstdc++-v3/ChangeLog:
* acinclude.m4 (_GLIBCXX_HAVE_LSEEK): Define.
* config.h.in: Regenerate.
* configure: Regenerate.
* src/filesystem/ops-common.h (copy_file_sendfile): Define new
function for sendfile logic. Loop to support large files. Skip
zero-length files.
(do_copy_file): Use it.
Signed-off-by: Jannik Glückert <jannik.glueckert@gmail.com>
diff --git a/libstdc++-v3/acinclude.m4 b/libstdc++-v3/acinclude.m4
index 5136c0571e8..85a09a5a869 100644
--- a/libstdc++-v3/acinclude.m4
+++ b/libstdc++-v3/acinclude.m4
@@ -4583,6 +4583,7 @@ dnl _GLIBCXX_USE_FCHMOD
dnl _GLIBCXX_USE_FCHMODAT
dnl _GLIBCXX_USE_SENDFILE
dnl HAVE_LINK
+dnl HAVE_LSEEK
dnl HAVE_READLINK
dnl HAVE_SYMLINK
dnl
@@ -4718,25 +4719,6 @@ dnl
if test $glibcxx_cv_fchmodat = yes; then
AC_DEFINE(_GLIBCXX_USE_FCHMODAT, 1, [Define if fchmodat is available in <sys/stat.h>.])
fi
-dnl
- AC_CACHE_CHECK([for sendfile that can copy files],
- glibcxx_cv_sendfile, [dnl
- case "${target_os}" in
- gnu* | linux* | solaris* | uclinux*)
- GCC_TRY_COMPILE_OR_LINK(
- [#include <sys/sendfile.h>],
- [sendfile(1, 2, (off_t*)0, sizeof 1);],
- [glibcxx_cv_sendfile=yes],
- [glibcxx_cv_sendfile=no])
- ;;
- *)
- glibcxx_cv_sendfile=no
- ;;
- esac
- ])
- if test $glibcxx_cv_sendfile = yes; then
- AC_DEFINE(_GLIBCXX_USE_SENDFILE, 1, [Define if sendfile is available in <sys/sendfile.h>.])
- fi
dnl
AC_CACHE_CHECK([for link],
glibcxx_cv_link, [dnl
@@ -4749,6 +4731,18 @@ dnl
if test $glibcxx_cv_link = yes; then
AC_DEFINE(HAVE_LINK, 1, [Define if link is available in <unistd.h>.])
fi
+dnl
+ AC_CACHE_CHECK([for lseek],
+ glibcxx_cv_lseek, [dnl
+ GCC_TRY_COMPILE_OR_LINK(
+ [#include <unistd.h>],
+ [lseek(1, 0, SEEK_SET);],
+ [glibcxx_cv_lseek=yes],
+ [glibcxx_cv_lseek=no])
+ ])
+ if test $glibcxx_cv_lseek = yes; then
+ AC_DEFINE(HAVE_LSEEK, 1, [Define if lseek is available in <unistd.h>.])
+ fi
dnl
AC_CACHE_CHECK([for readlink],
glibcxx_cv_readlink, [dnl
@@ -4785,6 +4779,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 sendfile that can copy files],
+ glibcxx_cv_sendfile, [dnl
+ case "${target_os}" in
+ gnu* | linux* | solaris* | uclinux*)
+ GCC_TRY_COMPILE_OR_LINK(
+ [#include <sys/sendfile.h>],
+ [sendfile(1, 2, (off_t*)0, sizeof 1);],
+ [glibcxx_cv_sendfile=yes],
+ [glibcxx_cv_sendfile=no])
+ ;;
+ *)
+ glibcxx_cv_sendfile=no
+ ;;
+ esac
+ ])
+ if test $glibcxx_cv_sendfile = yes && test $glibcxx_cv_lseek = yes; then
+ AC_DEFINE(_GLIBCXX_USE_SENDFILE, 1, [Define if sendfile is available in <sys/sendfile.h>.])
+ fi
dnl
AC_CACHE_CHECK([for fdopendir],
glibcxx_cv_fdopendir, [dnl
diff --git a/libstdc++-v3/src/filesystem/ops-common.h b/libstdc++-v3/src/filesystem/ops-common.h
index c95511b5c95..7874a95488a 100644
--- a/libstdc++-v3/src/filesystem/ops-common.h
+++ b/libstdc++-v3/src/filesystem/ops-common.h
@@ -51,6 +51,7 @@
# include <ext/stdio_filebuf.h>
# ifdef _GLIBCXX_USE_SENDFILE
# include <sys/sendfile.h> // sendfile
+# include <unistd.h> // lseek
# endif
#endif
@@ -358,6 +359,34 @@ _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM
}
#ifdef NEED_DO_COPY_FILE
+#if defined _GLIBCXX_USE_SENDFILE && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS
+ bool
+ copy_file_sendfile(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;
+ off_t offset = 0;
+ ssize_t bytes_copied;
+ do
+ {
+ bytes_copied = ::sendfile(fd_out, fd_in, &offset, bytes_left);
+ bytes_left -= bytes_copied;
+ }
+ while (bytes_left > 0 && bytes_copied > 0);
+ if (bytes_copied < 0)
+ {
+ ::lseek(fd_out, 0, SEEK_SET);
+ return false;
+ }
+ return true;
+ }
+#endif
bool
do_copy_file(const char_type* from, const char_type* to,
std::filesystem::copy_options_existing_file options,
@@ -498,28 +527,31 @@ _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM
return false;
}
- size_t count = from_st->st_size;
+ bool has_copied = false;
+
#if defined _GLIBCXX_USE_SENDFILE && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS
- off_t offset = 0;
- ssize_t n = ::sendfile(out.fd, in.fd, &offset, count);
- if (n < 0 && errno != ENOSYS && errno != EINVAL)
+ if (!has_copied)
+ has_copied = copy_file_sendfile(in.fd, out.fd, from_st->st_size);
+ if (!has_copied)
{
- ec.assign(errno, std::generic_category());
- return false;
- }
- if ((size_t)n == count)
- {
- if (!out.close() || !in.close())
+ if (errno != ENOSYS && errno != EINVAL)
{
ec.assign(errno, std::generic_category());
return false;
}
- ec.clear();
- return true;
}
- else if (n > 0)
- count -= n;
-#endif // _GLIBCXX_USE_SENDFILE
+#endif
+
+ if (has_copied)
+ {
+ if (!out.close() || !in.close())
+ {
+ ec.assign(errno, std::generic_category());
+ return false;
+ }
+ ec.clear();
+ return true;
+ }
using std::ios;
__gnu_cxx::stdio_filebuf<char> sbin(in.fd, ios::in|ios::binary);
@@ -530,29 +562,12 @@ _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM
if (sbout.is_open())
out.fd = -1;
-#ifdef _GLIBCXX_USE_SENDFILE
- if (n != 0)
- {
- if (n < 0)
- n = 0;
-
- const auto p1 = sbin.pubseekoff(n, ios::beg, ios::in);
- const auto p2 = sbout.pubseekoff(n, ios::beg, ios::out);
-
- const std::streampos errpos(std::streamoff(-1));
- if (p1 == errpos || p2 == errpos)
- {
- ec = std::make_error_code(std::errc::io_error);
- return false;
- }
- }
-#endif
-
- if (count && !(std::ostream(&sbout) << &sbin))
+ if (from_st->st_size && !(std::ostream(&sbout) << &sbin))
{
ec = std::make_error_code(std::errc::io_error);
return false;
}
+
if (!sbout.close() || !sbin.close())
{
ec.assign(errno, std::generic_category());
On Wed, 22 Mar 2023 at 12:14, Jonathan Wakely wrote:
>
>
> On Mon, 20 Mar 2023 at 22:30, Jonathan Wakely via Libstdc++ <
> libstdc++@gcc.gnu.org> wrote:
>
>> On 20/03/23 22:27 +0000, Jonathan Wakely wrote:
>> >On 06/03/23 20:52 +0100, Jannik Glückert wrote:
>> >>we were previously only using sendfile for files smaller than 2GB, as
>> >>sendfile needs to be called repeatedly for files bigger than that.
>> >>
>> >>some quick numbers, copying a 16GB file, average of 10 repetitions:
>> >> old:
>> >> real: 13.4s
>> >> user: 0.14s
>> >> sys : 7.43s
>> >> new:
>> >> real: 8.90s
>> >> user: 0.00s
>> >> sys : 3.68s
>> >>
>> >>Additionally, this fixes
>> >>https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108178
>> >>
>> >>libstdc++-v3/ChangeLog:
>> >>
>> >> * acinclude.m4 (_GLIBCXX_HAVE_LSEEK): define
>> >> * config.h.in: Regenerate.
>> >> * configure: Regenerate.
>> >> * src/filesystem/ops-common.h: enable sendfile for files
>> >> >2GB in std::filesystem::copy_file, skip zero-length files
>>
>> Also, the ChangeLog entry needs to be indented with tabs, name the
>> changed functions, and should be complete sentences, e.g.
>>
>> * acinclude.m4 (_GLIBCXX_HAVE_LSEEK): Define.
>> * config.h.in: Regenerate.
>> * configure: Regenerate.
>> * src/filesystem/ops-common.h (copy_file_sendfile): Define new
>> function for sendfile logic. Loop to support large files. Skip
>> zero-length files.
>> (do_copy_file): Use it.
>>
>>
> Here's what I plan to commit in a few weeks when GCC 14 Stage 1 opens.
>
>
>
And similarly for the copy_file_range change.
commit 2ad500e358c03ef63af1540d44645df582a4809c
Author: Jannik Glückert <jannik.glueckert@gmail.com>
Date: Wed Mar 8 18:37:43 2023
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>
diff --git a/libstdc++-v3/acinclude.m4 b/libstdc++-v3/acinclude.m4
index 85a09a5a869..4cf02dc6e4e 100644
--- a/libstdc++-v3/acinclude.m4
+++ b/libstdc++-v3/acinclude.m4
@@ -4581,6 +4581,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
@@ -4779,6 +4780,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
diff --git a/libstdc++-v3/src/filesystem/ops-common.h b/libstdc++-v3/src/filesystem/ops-common.h
index 7874a95488a..906436b484e 100644
--- a/libstdc++-v3/src/filesystem/ops-common.h
+++ b/libstdc++-v3/src/filesystem/ops-common.h
@@ -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);
On Wed, 22 Mar 2023 at 12:18, Jonathan Wakely wrote:
> On Wed, 22 Mar 2023 at 12:14, Jonathan Wakely wrote:
>
>>
>>
>> On Mon, 20 Mar 2023 at 22:30, Jonathan Wakely via Libstdc++ <
>> libstdc++@gcc.gnu.org> wrote:
>>
>>> On 20/03/23 22:27 +0000, Jonathan Wakely wrote:
>>> >On 06/03/23 20:52 +0100, Jannik Glückert wrote:
>>> >>we were previously only using sendfile for files smaller than 2GB, as
>>> >>sendfile needs to be called repeatedly for files bigger than that.
>>> >>
>>> >>some quick numbers, copying a 16GB file, average of 10 repetitions:
>>> >> old:
>>> >> real: 13.4s
>>> >> user: 0.14s
>>> >> sys : 7.43s
>>> >> new:
>>> >> real: 8.90s
>>> >> user: 0.00s
>>> >> sys : 3.68s
>>> >>
>>> >>Additionally, this fixes
>>> >>https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108178
>>> >>
>>> >>libstdc++-v3/ChangeLog:
>>> >>
>>> >> * acinclude.m4 (_GLIBCXX_HAVE_LSEEK): define
>>> >> * config.h.in: Regenerate.
>>> >> * configure: Regenerate.
>>> >> * src/filesystem/ops-common.h: enable sendfile for files
>>> >> >2GB in std::filesystem::copy_file, skip zero-length files
>>>
>>> Also, the ChangeLog entry needs to be indented with tabs, name the
>>> changed functions, and should be complete sentences, e.g.
>>>
>>> * acinclude.m4 (_GLIBCXX_HAVE_LSEEK): Define.
>>> * config.h.in: Regenerate.
>>> * configure: Regenerate.
>>> * src/filesystem/ops-common.h (copy_file_sendfile): Define new
>>> function for sendfile logic. Loop to support large files. Skip
>>> zero-length files.
>>> (do_copy_file): Use it.
>>>
>>>
>> Here's what I plan to commit in a few weeks when GCC 14 Stage 1 opens.
>>
>>
>>
> And similarly for the copy_file_range change.
>
And finally, here's the fix for PR libstdc++/108178, replacing the
zero-size check with checking for EOF in the source file
commit 3d994f1998c8f2efc2c8f5744615e92661bde46f
Author: Jonathan Wakely <jwakely@redhat.com>
Date: Tue Mar 21 12:29:08 2023
libstdc++: Make std::filesystem::copy_file work for procfs [PR108178]
The size reported by stat is always zero for some special files such as
those under /proc, which means the current copy_file implementation
thinks there is nothing to copy. Instead of trusting the stat value, try
to read a character from a streambuf and check for EOF.
libstdc++-v3/ChangeLog:
PR libstdc++/108178
* src/filesystem/ops-common.h (do_copy_file): Check for empty
files by trying to read a character.
* testsuite/27_io/filesystem/operations/copy_file_108178.cc:
New test.
diff --git a/libstdc++-v3/src/filesystem/ops-common.h b/libstdc++-v3/src/filesystem/ops-common.h
index 906436b484e..a28cbeb10b5 100644
--- a/libstdc++-v3/src/filesystem/ops-common.h
+++ b/libstdc++-v3/src/filesystem/ops-common.h
@@ -618,11 +618,16 @@ _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM
if (sbout.is_open())
out.fd = -1;
- if (from_st->st_size && !(std::ostream(&sbout) << &sbin))
- {
- ec = std::make_error_code(std::errc::io_error);
- return false;
- }
+ // ostream::operator<<(streambuf*) fails if it extracts no characters,
+ // so don't try to use it for empty files. But from_st->st_size == 0 for
+ // some special files (e.g. procfs, see PR libstdc++/108178) so just try
+ // to read a character to decide whether there is anything to copy or not.
+ if (sbin.sgetc() != char_traits<char>::eof())
+ if (!(std::ostream(&sbout) << &sbin))
+ {
+ ec = std::make_error_code(std::errc::io_error);
+ return false;
+ }
if (!sbout.close() || !sbin.close())
{
diff --git a/libstdc++-v3/testsuite/27_io/filesystem/operations/copy_file_108178.cc b/libstdc++-v3/testsuite/27_io/filesystem/operations/copy_file_108178.cc
new file mode 100644
index 00000000000..25135834e21
--- /dev/null
+++ b/libstdc++-v3/testsuite/27_io/filesystem/operations/copy_file_108178.cc
@@ -0,0 +1,33 @@
+// { dg-do run { target c++17 } }
+// { dg-require-filesystem-ts "" }
+
+// C++17 30.10.15.4 Copy [fs.op.copy_file]
+
+#include <filesystem>
+#include <fstream>
+#include <unistd.h> // getpid
+#include <testsuite_fs.h>
+#include <testsuite_hooks.h>
+
+namespace fs = std::filesystem;
+
+void
+test_procfs() // PR libstdc++/108178
+{
+ auto pid = ::getpid();
+ std::string from = "/proc/" + std::to_string(pid) + "/status";
+ if (fs::exists(from))
+ {
+ auto to = __gnu_test::nonexistent_path();
+ fs::copy_file(from, to);
+ std::ifstream f(to);
+ VERIFY(f.is_open());
+ VERIFY(f.peek() != std::char_traits<char>::eof());
+ fs::remove(to);
+ }
+}
+
+int main()
+{
+ test_procfs();
+}
On Wed, 22 Mar 2023 at 12:14, Jonathan Wakely <jwakely@redhat.com> wrote:
>
>
> On Mon, 20 Mar 2023 at 22:30, Jonathan Wakely via Libstdc++ <
> libstdc++@gcc.gnu.org> wrote:
>
>> On 20/03/23 22:27 +0000, Jonathan Wakely wrote:
>> >On 06/03/23 20:52 +0100, Jannik Glückert wrote:
>> >>we were previously only using sendfile for files smaller than 2GB, as
>> >>sendfile needs to be called repeatedly for files bigger than that.
>> >>
>> >>some quick numbers, copying a 16GB file, average of 10 repetitions:
>> >> old:
>> >> real: 13.4s
>> >> user: 0.14s
>> >> sys : 7.43s
>> >> new:
>> >> real: 8.90s
>> >> user: 0.00s
>> >> sys : 3.68s
>> >>
>> >>Additionally, this fixes
>> >>https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108178
>> >>
>> >>libstdc++-v3/ChangeLog:
>> >>
>> >> * acinclude.m4 (_GLIBCXX_HAVE_LSEEK): define
>> >> * config.h.in: Regenerate.
>> >> * configure: Regenerate.
>> >> * src/filesystem/ops-common.h: enable sendfile for files
>> >> >2GB in std::filesystem::copy_file, skip zero-length files
>>
>> Also, the ChangeLog entry needs to be indented with tabs, name the
>> changed functions, and should be complete sentences, e.g.
>>
>> * acinclude.m4 (_GLIBCXX_HAVE_LSEEK): Define.
>> * config.h.in: Regenerate.
>> * configure: Regenerate.
>> * src/filesystem/ops-common.h (copy_file_sendfile): Define new
>> function for sendfile logic. Loop to support large files. Skip
>> zero-length files.
>> (do_copy_file): Use it.
>>
>>
> Here's what I plan to commit in a few weeks when GCC 14 Stage 1 opens.
>
>
>
Pushed to trunk now (after testing on btrfs, xfs, and tmpfs using kernel
6.3 and on xfs using kernel 3.10).
On Wed, 22 Mar 2023 at 12:18, Jonathan Wakely <jwakely@redhat.com> wrote:
> On Wed, 22 Mar 2023 at 12:14, Jonathan Wakely wrote:
>
>>
>>
>> On Mon, 20 Mar 2023 at 22:30, Jonathan Wakely via Libstdc++ <
>> libstdc++@gcc.gnu.org> wrote:
>>
>>> On 20/03/23 22:27 +0000, Jonathan Wakely wrote:
>>> >On 06/03/23 20:52 +0100, Jannik Glückert wrote:
>>> >>we were previously only using sendfile for files smaller than 2GB, as
>>> >>sendfile needs to be called repeatedly for files bigger than that.
>>> >>
>>> >>some quick numbers, copying a 16GB file, average of 10 repetitions:
>>> >> old:
>>> >> real: 13.4s
>>> >> user: 0.14s
>>> >> sys : 7.43s
>>> >> new:
>>> >> real: 8.90s
>>> >> user: 0.00s
>>> >> sys : 3.68s
>>> >>
>>> >>Additionally, this fixes
>>> >>https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108178
>>> >>
>>> >>libstdc++-v3/ChangeLog:
>>> >>
>>> >> * acinclude.m4 (_GLIBCXX_HAVE_LSEEK): define
>>> >> * config.h.in: Regenerate.
>>> >> * configure: Regenerate.
>>> >> * src/filesystem/ops-common.h: enable sendfile for files
>>> >> >2GB in std::filesystem::copy_file, skip zero-length files
>>>
>>> Also, the ChangeLog entry needs to be indented with tabs, name the
>>> changed functions, and should be complete sentences, e.g.
>>>
>>> * acinclude.m4 (_GLIBCXX_HAVE_LSEEK): Define.
>>> * config.h.in: Regenerate.
>>> * configure: Regenerate.
>>> * src/filesystem/ops-common.h (copy_file_sendfile): Define new
>>> function for sendfile logic. Loop to support large files. Skip
>>> zero-length files.
>>> (do_copy_file): Use it.
>>>
>>>
>> Here's what I plan to commit in a few weeks when GCC 14 Stage 1 opens.
>>
>>
>>
> And similarly for the copy_file_range change.
>
>
This one is also now pushed to trunk (after testing on btrfs, xfs, and
tmpfs using kernel 6.3 and on xfs using kernel 3.10).
>
>
On Wed, 22 Mar 2023 at 12:20, Jonathan Wakely <jwakely@redhat.com> wrote:
> On Wed, 22 Mar 2023 at 12:18, Jonathan Wakely wrote:
>
>> On Wed, 22 Mar 2023 at 12:14, Jonathan Wakely wrote:
>>
>>>
>>>
>>> On Mon, 20 Mar 2023 at 22:30, Jonathan Wakely via Libstdc++ <
>>> libstdc++@gcc.gnu.org> wrote:
>>>
>>>> On 20/03/23 22:27 +0000, Jonathan Wakely wrote:
>>>> >On 06/03/23 20:52 +0100, Jannik Glückert wrote:
>>>> >>we were previously only using sendfile for files smaller than 2GB, as
>>>> >>sendfile needs to be called repeatedly for files bigger than that.
>>>> >>
>>>> >>some quick numbers, copying a 16GB file, average of 10 repetitions:
>>>> >> old:
>>>> >> real: 13.4s
>>>> >> user: 0.14s
>>>> >> sys : 7.43s
>>>> >> new:
>>>> >> real: 8.90s
>>>> >> user: 0.00s
>>>> >> sys : 3.68s
>>>> >>
>>>> >>Additionally, this fixes
>>>> >>https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108178
>>>> >>
>>>> >>libstdc++-v3/ChangeLog:
>>>> >>
>>>> >> * acinclude.m4 (_GLIBCXX_HAVE_LSEEK): define
>>>> >> * config.h.in: Regenerate.
>>>> >> * configure: Regenerate.
>>>> >> * src/filesystem/ops-common.h: enable sendfile for files
>>>> >> >2GB in std::filesystem::copy_file, skip zero-length files
>>>>
>>>> Also, the ChangeLog entry needs to be indented with tabs, name the
>>>> changed functions, and should be complete sentences, e.g.
>>>>
>>>> * acinclude.m4 (_GLIBCXX_HAVE_LSEEK): Define.
>>>> * config.h.in: Regenerate.
>>>> * configure: Regenerate.
>>>> * src/filesystem/ops-common.h (copy_file_sendfile): Define new
>>>> function for sendfile logic. Loop to support large files. Skip
>>>> zero-length files.
>>>> (do_copy_file): Use it.
>>>>
>>>>
>>> Here's what I plan to commit in a few weeks when GCC 14 Stage 1 opens.
>>>
>>>
>>>
>> And similarly for the copy_file_range change.
>>
>
> And finally, here's the fix for PR libstdc++/108178, replacing the
> zero-size check with checking for EOF in the source file
>
>
Also now pushed to trunk.
From b55eb8dccaa44f07d8acbe6294326a46c920b04f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jannik=20Gl=C3=BCckert?= <jannik.glueckert@gmail.com>
Date: Mon, 6 Mar 2023 20:52:08 +0100
Subject: [PATCH 1/2] libstdc++: also use sendfile for big files
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
we were previously only using sendfile for files smaller than 2GB, as
sendfile needs to be called repeatedly for files bigger than that.
some quick numbers, copying a 16GB file, average of 10 repetitions:
old:
real: 13.4s
user: 0.14s
sys : 7.43s
new:
real: 8.90s
user: 0.00s
sys : 3.68s
Additionally, this fixes
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108178
libstdc++-v3/ChangeLog:
* acinclude.m4 (_GLIBCXX_HAVE_LSEEK): define
* config.h.in: Regenerate.
* configure: Regenerate.
* src/filesystem/ops-common.h: enable sendfile for files
>2GB in std::filesystem::copy_file, skip zero-length files
Signed-off-by: Jannik Glückert <jannik.glueckert@gmail.com>
---
libstdc++-v3/acinclude.m4 | 51 +++++----
libstdc++-v3/config.h.in | 3 +
libstdc++-v3/configure | 127 ++++++++++++++++-------
libstdc++-v3/src/filesystem/ops-common.h | 86 ++++++++-------
4 files changed, 175 insertions(+), 92 deletions(-)
@@ -4583,6 +4583,7 @@ dnl _GLIBCXX_USE_FCHMOD
dnl _GLIBCXX_USE_FCHMODAT
dnl _GLIBCXX_USE_SENDFILE
dnl HAVE_LINK
+dnl HAVE_LSEEK
dnl HAVE_READLINK
dnl HAVE_SYMLINK
dnl
@@ -4718,25 +4719,6 @@ dnl
if test $glibcxx_cv_fchmodat = yes; then
AC_DEFINE(_GLIBCXX_USE_FCHMODAT, 1, [Define if fchmodat is available in <sys/stat.h>.])
fi
-dnl
- AC_CACHE_CHECK([for sendfile that can copy files],
- glibcxx_cv_sendfile, [dnl
- case "${target_os}" in
- gnu* | linux* | solaris* | uclinux*)
- GCC_TRY_COMPILE_OR_LINK(
- [#include <sys/sendfile.h>],
- [sendfile(1, 2, (off_t*)0, sizeof 1);],
- [glibcxx_cv_sendfile=yes],
- [glibcxx_cv_sendfile=no])
- ;;
- *)
- glibcxx_cv_sendfile=no
- ;;
- esac
- ])
- if test $glibcxx_cv_sendfile = yes; then
- AC_DEFINE(_GLIBCXX_USE_SENDFILE, 1, [Define if sendfile is available in <sys/sendfile.h>.])
- fi
dnl
AC_CACHE_CHECK([for link],
glibcxx_cv_link, [dnl
@@ -4749,6 +4731,18 @@ dnl
if test $glibcxx_cv_link = yes; then
AC_DEFINE(HAVE_LINK, 1, [Define if link is available in <unistd.h>.])
fi
+dnl
+ AC_CACHE_CHECK([for lseek],
+ glibcxx_cv_lseek, [dnl
+ GCC_TRY_COMPILE_OR_LINK(
+ [#include <unistd.h>],
+ [lseek(1, 0, SEEK_SET);],
+ [glibcxx_cv_lseek=yes],
+ [glibcxx_cv_lseek=no])
+ ])
+ if test $glibcxx_cv_lseek = yes; then
+ AC_DEFINE(HAVE_LSEEK, 1, [Define if lseek is available in <unistd.h>.])
+ fi
dnl
AC_CACHE_CHECK([for readlink],
glibcxx_cv_readlink, [dnl
@@ -4785,6 +4779,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 sendfile that can copy files],
+ glibcxx_cv_sendfile, [dnl
+ case "${target_os}" in
+ gnu* | linux* | solaris* | uclinux*)
+ GCC_TRY_COMPILE_OR_LINK(
+ [#include <sys/sendfile.h>],
+ [sendfile(1, 2, (off_t*)0, sizeof 1);],
+ [glibcxx_cv_sendfile=yes],
+ [glibcxx_cv_sendfile=no])
+ ;;
+ *)
+ glibcxx_cv_sendfile=no
+ ;;
+ esac
+ ])
+ if test $glibcxx_cv_sendfile = yes && test $glibcxx_cv_lseek = yes; then
+ AC_DEFINE(_GLIBCXX_USE_SENDFILE, 1, [Define if sendfile is available in <sys/sendfile.h>.])
+ fi
dnl
AC_CACHE_CHECK([for fdopendir],
glibcxx_cv_fdopendir, [dnl
@@ -51,6 +51,7 @@
# include <ext/stdio_filebuf.h>
# ifdef _GLIBCXX_USE_SENDFILE
# include <sys/sendfile.h> // sendfile
+# include <unistd.h> // lseek
# endif
#endif
@@ -358,6 +359,32 @@ _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM
}
#ifdef NEED_DO_COPY_FILE
+#if defined _GLIBCXX_USE_SENDFILE && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS
+ bool
+ copy_file_sendfile(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;
+ off_t offset = 0;
+ ssize_t bytes_copied;
+ do {
+ bytes_copied = ::sendfile(fd_out, fd_in, &offset, bytes_left);
+ bytes_left -= bytes_copied;
+ } while (bytes_left > 0 && bytes_copied > 0);
+ if (bytes_copied < 0)
+ {
+ ::lseek(fd_out, 0, SEEK_SET);
+ return false;
+ }
+ return true;
+ }
+#endif
bool
do_copy_file(const char_type* from, const char_type* to,
std::filesystem::copy_options_existing_file options,
@@ -498,28 +525,30 @@ _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM
return false;
}
- size_t count = from_st->st_size;
+ bool has_copied = false;
+
#if defined _GLIBCXX_USE_SENDFILE && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS
- off_t offset = 0;
- ssize_t n = ::sendfile(out.fd, in.fd, &offset, count);
- if (n < 0 && errno != ENOSYS && errno != EINVAL)
+ if (!has_copied)
+ has_copied = copy_file_sendfile(in.fd, out.fd, from_st->st_size);
+ if (!has_copied)
{
- ec.assign(errno, std::generic_category());
- return false;
+ if (errno != ENOSYS && errno != EINVAL)
+ {
+ ec.assign(errno, std::generic_category());
+ return false;
+ }
}
- if ((size_t)n == count)
+#endif
+
+ if (has_copied)
{
- if (!out.close() || !in.close())
- {
- ec.assign(errno, std::generic_category());
- return false;
- }
- ec.clear();
- return true;
+ if (!out.close() || !in.close())
+ {
+ ec.assign(errno, std::generic_category());
+ return false;
+ }
+ return true;
}
- else if (n > 0)
- count -= n;
-#endif // _GLIBCXX_USE_SENDFILE
using std::ios;
__gnu_cxx::stdio_filebuf<char> sbin(in.fd, ios::in|ios::binary);
@@ -530,29 +559,12 @@ _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM
if (sbout.is_open())
out.fd = -1;
-#ifdef _GLIBCXX_USE_SENDFILE
- if (n != 0)
+ if (!(std::ostream(&sbout) << &sbin))
{
- if (n < 0)
- n = 0;
-
- const auto p1 = sbin.pubseekoff(n, ios::beg, ios::in);
- const auto p2 = sbout.pubseekoff(n, ios::beg, ios::out);
-
- const std::streampos errpos(std::streamoff(-1));
- if (p1 == errpos || p2 == errpos)
- {
- ec = std::make_error_code(std::errc::io_error);
- return false;
- }
+ ec = std::make_error_code(std::errc::io_error);
+ return false;
}
-#endif
- if (count && !(std::ostream(&sbout) << &sbin))
- {
- ec = std::make_error_code(std::errc::io_error);
- return false;
- }
if (!sbout.close() || !sbin.close())
{
ec.assign(errno, std::generic_category());
--
2.39.2