1515#include < filesystem>
1616#include < iterator>
1717#include < string_view>
18+ #include < system_error>
1819#include < type_traits>
1920#include < vector>
2021
3233# include < dirent.h>
3334# include < sys/stat.h>
3435# include < sys/statvfs.h>
36+ # include < sys/types.h>
3537# include < unistd.h>
3638#endif
3739#include < fcntl.h> /* values for fchmodat */
3840#include < time.h>
3941
42+ // since Linux 4.5 and FreeBSD 13, but the Linux libc wrapper is only provided by glibc and musl
43+ #if (defined(__linux__) && (defined(__GLIBC__) || _LIBCPP_HAS_MUSL_LIBC)) || defined(__FreeBSD__)
44+ # define _LIBCPP_FILESYSTEM_USE_COPY_FILE_RANGE
45+ #endif
4046#if __has_include(<sys/sendfile.h>)
4147# include < sys/sendfile.h>
4248# define _LIBCPP_FILESYSTEM_USE_SENDFILE
4349#elif defined(__APPLE__) || __has_include(<copyfile.h>)
4450# include < copyfile.h>
4551# define _LIBCPP_FILESYSTEM_USE_COPYFILE
4652#else
47- # include < fstream>
4853# define _LIBCPP_FILESYSTEM_USE_FSTREAM
4954#endif
5055
56+ // sendfile and copy_file_range need to fall back
57+ // to the fstream implementation for special files
58+ #if (defined(_LIBCPP_FILESYSTEM_USE_SENDFILE) || defined(_LIBCPP_FILESYSTEM_USE_COPY_FILE_RANGE) || \
59+ defined (_LIBCPP_FILESYSTEM_USE_FSTREAM)) && \
60+ _LIBCPP_HAS_LOCALIZATION
61+ # include < fstream>
62+ # define _LIBCPP_FILESYSTEM_NEED_FSTREAM
63+ #endif
64+
5165#if defined(__ELF__) && defined(_LIBCPP_LINK_RT_LIB)
5266# pragma comment(lib, "rt")
5367#endif
@@ -178,9 +192,83 @@ void __copy(const path& from, const path& to, copy_options options, error_code*
178192namespace detail {
179193namespace {
180194
195+ #if defined(_LIBCPP_FILESYSTEM_NEED_FSTREAM)
196+ bool copy_file_impl_fstream (FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
197+ ifstream in;
198+ in.__open (read_fd.fd , ios::binary);
199+ if (!in.is_open ()) {
200+ // This assumes that __open didn't reset the error code.
201+ ec = capture_errno ();
202+ return false ;
203+ }
204+ read_fd.fd = -1 ;
205+ ofstream out;
206+ out.__open (write_fd.fd , ios::binary);
207+ if (!out.is_open ()) {
208+ ec = capture_errno ();
209+ return false ;
210+ }
211+ write_fd.fd = -1 ;
212+
213+ if (in.good () && out.good ()) {
214+ using InIt = istreambuf_iterator<char >;
215+ using OutIt = ostreambuf_iterator<char >;
216+ InIt bin (in);
217+ InIt ein;
218+ OutIt bout (out);
219+ copy (bin, ein, bout);
220+ }
221+ if (out.fail () || in.fail ()) {
222+ ec = make_error_code (errc::io_error);
223+ return false ;
224+ }
225+
226+ ec.clear ();
227+ return true ;
228+ }
229+ #endif
230+
231+ #if defined(_LIBCPP_FILESYSTEM_USE_COPY_FILE_RANGE)
232+ bool copy_file_impl_copy_file_range (FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
233+ size_t count = read_fd.get_stat ().st_size ;
234+ // a zero-length file is either empty, or not copyable by this syscall
235+ // return early to avoid the syscall cost
236+ if (count == 0 ) {
237+ ec = {EINVAL, generic_category ()};
238+ return false ;
239+ }
240+ // do not modify the fd positions as copy_file_impl_sendfile may be called after a partial copy
241+ off_t off_in = 0 ;
242+ off_t off_out = 0 ;
243+ do {
244+ ssize_t res;
245+
246+ if ((res = ::copy_file_range (read_fd.fd , &off_in, write_fd.fd , &off_out, count, 0 )) == -1 ) {
247+ ec = capture_errno ();
248+ return false ;
249+ }
250+ count -= res;
251+ } while (count > 0 );
252+
253+ ec.clear ();
254+
255+ return true ;
256+ }
257+ #endif
258+
181259#if defined(_LIBCPP_FILESYSTEM_USE_SENDFILE)
182- bool copy_file_impl (FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
260+ bool copy_file_impl_sendfile (FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
183261 size_t count = read_fd.get_stat ().st_size ;
262+ // a zero-length file is either empty, or not copyable by this syscall
263+ // return early to avoid the syscall cost
264+ // however, we can't afford this luxury in the no-locale build,
265+ // as we can't utilize the fstream impl to copy empty files
266+ # if _LIBCPP_HAS_LOCALIZATION
267+ if (count == 0 ) {
268+ ec = {EINVAL, generic_category ()};
269+ return false ;
270+ }
271+ # endif
184272 do {
185273 ssize_t res;
186274 if ((res = ::sendfile (write_fd.fd , read_fd.fd , nullptr , count)) == -1 ) {
@@ -194,6 +282,54 @@ bool copy_file_impl(FileDescriptor& read_fd, FileDescriptor& write_fd, error_cod
194282
195283 return true ;
196284}
285+ #endif
286+
287+ #if defined(_LIBCPP_FILESYSTEM_USE_COPY_FILE_RANGE) || defined(_LIBCPP_FILESYSTEM_USE_SENDFILE)
288+ // If we have copy_file_range or sendfile, try both in succession (if available).
289+ // If both fail, fall back to using fstream.
290+ bool copy_file_impl (FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
291+ # if defined(_LIBCPP_FILESYSTEM_USE_COPY_FILE_RANGE)
292+ if (copy_file_impl_copy_file_range (read_fd, write_fd, ec)) {
293+ return true ;
294+ }
295+ // EINVAL: src and dst are the same file (this is not cheaply
296+ // detectable from userspace)
297+ // EINVAL: copy_file_range is unsupported for this file type by the
298+ // underlying filesystem
299+ // ENOTSUP: undocumented, can arise with old kernels and NFS
300+ // EOPNOTSUPP: filesystem does not implement copy_file_range
301+ // ETXTBSY: src or dst is an active swapfile (nonsensical, but allowed
302+ // with normal copying)
303+ // EXDEV: src and dst are on different filesystems that do not support
304+ // cross-fs copy_file_range
305+ // ENOENT: undocumented, can arise with CIFS
306+ // ENOSYS: unsupported by kernel or blocked by seccomp
307+ if (ec.value () != EINVAL && ec.value () != ENOTSUP && ec.value () != EOPNOTSUPP && ec.value () != ETXTBSY &&
308+ ec.value () != EXDEV && ec.value () != ENOENT && ec.value () != ENOSYS) {
309+ return false ;
310+ }
311+ ec.clear ();
312+ # endif
313+
314+ # if defined(_LIBCPP_FILESYSTEM_USE_SENDFILE)
315+ if (copy_file_impl_sendfile (read_fd, write_fd, ec)) {
316+ return true ;
317+ }
318+ // EINVAL: unsupported file type
319+ if (ec.value () != EINVAL) {
320+ return false ;
321+ }
322+ ec.clear ();
323+ # endif
324+
325+ # if defined(_LIBCPP_FILESYSTEM_NEED_FSTREAM)
326+ return copy_file_impl_fstream (read_fd, write_fd, ec);
327+ # else
328+ // since iostreams are unavailable in the no-locale build, just fail after a failed sendfile
329+ ec.assign (EINVAL, std::system_category ());
330+ return false ;
331+ # endif
332+ }
197333#elif defined(_LIBCPP_FILESYSTEM_USE_COPYFILE)
198334bool copy_file_impl (FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
199335 struct CopyFileState {
@@ -217,37 +353,7 @@ bool copy_file_impl(FileDescriptor& read_fd, FileDescriptor& write_fd, error_cod
217353}
218354#elif defined(_LIBCPP_FILESYSTEM_USE_FSTREAM)
219355bool copy_file_impl (FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
220- ifstream in;
221- in.__open (read_fd.fd , ios::binary);
222- if (!in.is_open ()) {
223- // This assumes that __open didn't reset the error code.
224- ec = capture_errno ();
225- return false ;
226- }
227- read_fd.fd = -1 ;
228- ofstream out;
229- out.__open (write_fd.fd , ios::binary);
230- if (!out.is_open ()) {
231- ec = capture_errno ();
232- return false ;
233- }
234- write_fd.fd = -1 ;
235-
236- if (in.good () && out.good ()) {
237- using InIt = istreambuf_iterator<char >;
238- using OutIt = ostreambuf_iterator<char >;
239- InIt bin (in);
240- InIt ein;
241- OutIt bout (out);
242- copy (bin, ein, bout);
243- }
244- if (out.fail () || in.fail ()) {
245- ec = make_error_code (errc::io_error);
246- return false ;
247- }
248-
249- ec.clear ();
250- return true ;
356+ return copy_file_impl_fstream (read_fd, write_fd, ec);
251357}
252358#else
253359# error "Unknown implementation for copy_file_impl"
0 commit comments