15
15
#include < filesystem>
16
16
#include < iterator>
17
17
#include < string_view>
18
+ #include < system_error>
18
19
#include < type_traits>
19
20
#include < vector>
20
21
32
33
# include < dirent.h>
33
34
# include < sys/stat.h>
34
35
# include < sys/statvfs.h>
36
+ # include < sys/types.h>
35
37
# include < unistd.h>
36
38
#endif
37
39
#include < fcntl.h> /* values for fchmodat */
38
40
#include < time.h>
39
41
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
40
46
#if __has_include(<sys/sendfile.h>)
41
47
# include < sys/sendfile.h>
42
48
# define _LIBCPP_FILESYSTEM_USE_SENDFILE
43
49
#elif defined(__APPLE__) || __has_include(<copyfile.h>)
44
50
# include < copyfile.h>
45
51
# define _LIBCPP_FILESYSTEM_USE_COPYFILE
46
52
#else
47
- # include < fstream>
48
53
# define _LIBCPP_FILESYSTEM_USE_FSTREAM
49
54
#endif
50
55
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
+
51
65
#if defined(__ELF__) && defined(_LIBCPP_LINK_RT_LIB)
52
66
# pragma comment(lib, "rt")
53
67
#endif
@@ -178,9 +192,83 @@ void __copy(const path& from, const path& to, copy_options options, error_code*
178
192
namespace detail {
179
193
namespace {
180
194
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
+
181
259
#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) {
183
261
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
184
272
do {
185
273
ssize_t res;
186
274
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
194
282
195
283
return true ;
196
284
}
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
+ }
197
333
#elif defined(_LIBCPP_FILESYSTEM_USE_COPYFILE)
198
334
bool copy_file_impl (FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
199
335
struct CopyFileState {
@@ -217,37 +353,7 @@ bool copy_file_impl(FileDescriptor& read_fd, FileDescriptor& write_fd, error_cod
217
353
}
218
354
#elif defined(_LIBCPP_FILESYSTEM_USE_FSTREAM)
219
355
bool 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);
251
357
}
252
358
#else
253
359
# error "Unknown implementation for copy_file_impl"
0 commit comments