]> git.lizzy.rs Git - rust.git/blob - library/std/src/sys/unix/kernel_copy.rs
Auto merge of #79780 - camelid:use-summary_opts, r=GuillaumeGomez
[rust.git] / library / std / src / sys / unix / kernel_copy.rs
1 //! This module contains specializations that can offload `io::copy()` operations on file descriptor
2 //! containing types (`File`, `TcpStream`, etc.) to more efficient syscalls than `read(2)` and `write(2)`.
3 //!
4 //! Specialization is only applied to wholly std-owned types so that user code can't observe
5 //! that the `Read` and `Write` traits are not used.
6 //!
7 //! Since a copy operation involves a reader and writer side where each can consist of different types
8 //! and also involve generic wrappers (e.g. `Take`, `BufReader`) it is not practical to specialize
9 //! a single method on all possible combinations.
10 //!
11 //! Instead readers and writers are handled separately by the `CopyRead` and `CopyWrite` specialization
12 //! traits and then specialized on by the `Copier::copy` method.
13 //!
14 //! `Copier` uses the specialization traits to unpack the underlying file descriptors and
15 //! additional prerequisites and constraints imposed by the wrapper types.
16 //!
17 //! Once it has obtained all necessary pieces and brought any wrapper types into a state where they
18 //! can be safely bypassed it will attempt to use the `copy_file_range(2)`,
19 //! `sendfile(2)` or `splice(2)` syscalls to move data directly between file descriptors.
20 //! Since those syscalls have requirements that cannot be fully checked in advance and
21 //! gathering additional information about file descriptors would require additional syscalls
22 //! anyway it simply attempts to use them one after another (guided by inaccurate hints) to
23 //! figure out which one works and and falls back to the generic read-write copy loop if none of them
24 //! does.
25 //! Once a working syscall is found for a pair of file descriptors it will be called in a loop
26 //! until the copy operation is completed.
27 //!
28 //! Advantages of using these syscalls:
29 //!
30 //! * fewer context switches since reads and writes are coalesced into a single syscall
31 //!   and more bytes are transferred per syscall. This translates to higher throughput
32 //!   and fewer CPU cycles, at least for sufficiently large transfers to amortize the initial probing.
33 //! * `copy_file_range` creates reflink copies on CoW filesystems, thus moving less data and
34 //!   consuming less disk space
35 //! * `sendfile` and `splice` can perform zero-copy IO under some circumstances while
36 //!   a naive copy loop would move every byte through the CPU.
37 //!
38 //! Drawbacks:
39 //!
40 //! * copy operations smaller than the default buffer size can under some circumstances, especially
41 //!   on older kernels, incur more syscalls than the naive approach would. As mentioned above
42 //!   the syscall selection is guided by hints to minimize this possibility but they are not perfect.
43 //! * optimizations only apply to std types. If a user adds a custom wrapper type, e.g. to report
44 //!   progress, they can hit a performance cliff.
45 //! * complexity
46
47 use crate::cmp::min;
48 use crate::convert::TryInto;
49 use crate::fs::{File, Metadata};
50 use crate::io::copy::generic_copy;
51 use crate::io::{
52     BufRead, BufReader, BufWriter, Error, Read, Result, StderrLock, StdinLock, StdoutLock, Take,
53     Write,
54 };
55 use crate::mem::ManuallyDrop;
56 use crate::net::TcpStream;
57 use crate::os::unix::fs::FileTypeExt;
58 use crate::os::unix::io::{AsRawFd, FromRawFd, RawFd};
59 use crate::os::unix::net::UnixStream;
60 use crate::process::{ChildStderr, ChildStdin, ChildStdout};
61 use crate::ptr;
62 use crate::sync::atomic::{AtomicBool, Ordering};
63 use crate::sys::cvt;
64
65 #[cfg(test)]
66 mod tests;
67
68 pub(crate) fn copy_spec<R: Read + ?Sized, W: Write + ?Sized>(
69     read: &mut R,
70     write: &mut W,
71 ) -> Result<u64> {
72     let copier = Copier { read, write };
73     SpecCopy::copy(copier)
74 }
75
76 /// This type represents either the inferred `FileType` of a `RawFd` based on the source
77 /// type from which it was extracted or the actual metadata
78 ///
79 /// The methods on this type only provide hints, due to `AsRawFd` and `FromRawFd` the inferred
80 /// type may be wrong.
81 enum FdMeta {
82     /// We obtained the FD from a type that can contain any type of `FileType` and queried the metadata
83     /// because it is cheaper than probing all possible syscalls (reader side)
84     Metadata(Metadata),
85     Socket,
86     Pipe,
87     /// We don't have any metadata, e.g. because the original type was `File` which can represent
88     /// any `FileType` and we did not query the metadata either since it did not seem beneficial
89     /// (writer side)
90     NoneObtained,
91 }
92
93 impl FdMeta {
94     fn maybe_fifo(&self) -> bool {
95         match self {
96             FdMeta::Metadata(meta) => meta.file_type().is_fifo(),
97             FdMeta::Socket => false,
98             FdMeta::Pipe => true,
99             FdMeta::NoneObtained => true,
100         }
101     }
102
103     fn potential_sendfile_source(&self) -> bool {
104         match self {
105             // procfs erronously shows 0 length on non-empty readable files.
106             // and if a file is truly empty then a `read` syscall will determine that and skip the write syscall
107             // thus there would be benefit from attempting sendfile
108             FdMeta::Metadata(meta)
109                 if meta.file_type().is_file() && meta.len() > 0
110                     || meta.file_type().is_block_device() =>
111             {
112                 true
113             }
114             _ => false,
115         }
116     }
117
118     fn copy_file_range_candidate(&self) -> bool {
119         match self {
120             // copy_file_range will fail on empty procfs files. `read` can determine whether EOF has been reached
121             // without extra cost and skip the write, thus there is no benefit in attempting copy_file_range
122             FdMeta::Metadata(meta) if meta.is_file() && meta.len() > 0 => true,
123             FdMeta::NoneObtained => true,
124             _ => false,
125         }
126     }
127 }
128
129 struct CopyParams(FdMeta, Option<RawFd>);
130
131 struct Copier<'a, 'b, R: Read + ?Sized, W: Write + ?Sized> {
132     read: &'a mut R,
133     write: &'b mut W,
134 }
135
136 trait SpecCopy {
137     fn copy(self) -> Result<u64>;
138 }
139
140 impl<R: Read + ?Sized, W: Write + ?Sized> SpecCopy for Copier<'_, '_, R, W> {
141     default fn copy(self) -> Result<u64> {
142         generic_copy(self.read, self.write)
143     }
144 }
145
146 impl<R: CopyRead, W: CopyWrite> SpecCopy for Copier<'_, '_, R, W> {
147     fn copy(self) -> Result<u64> {
148         let (reader, writer) = (self.read, self.write);
149         let r_cfg = reader.properties();
150         let w_cfg = writer.properties();
151
152         // before direct operations on file descriptors ensure that all source and sink buffers are empty
153         let mut flush = || -> crate::io::Result<u64> {
154             let bytes = reader.drain_to(writer, u64::MAX)?;
155             // BufWriter buffered bytes have already been accounted for in earlier write() calls
156             writer.flush()?;
157             Ok(bytes)
158         };
159
160         let mut written = 0u64;
161
162         if let (CopyParams(input_meta, Some(readfd)), CopyParams(output_meta, Some(writefd))) =
163             (r_cfg, w_cfg)
164         {
165             written += flush()?;
166             let max_write = reader.min_limit();
167
168             if input_meta.copy_file_range_candidate() && output_meta.copy_file_range_candidate() {
169                 let result = copy_regular_files(readfd, writefd, max_write);
170                 result.update_take(reader);
171
172                 match result {
173                     CopyResult::Ended(bytes_copied) => return Ok(bytes_copied + written),
174                     CopyResult::Error(e, _) => return Err(e),
175                     CopyResult::Fallback(bytes) => written += bytes,
176                 }
177             }
178
179             // on modern kernels sendfile can copy from any mmapable type (some but not all regular files and block devices)
180             // to any writable file descriptor. On older kernels the writer side can only be a socket.
181             // So we just try and fallback if needed.
182             // If current file offsets + write sizes overflow it may also fail, we do not try to fix that and instead
183             // fall back to the generic copy loop.
184             if input_meta.potential_sendfile_source() {
185                 let result = sendfile_splice(SpliceMode::Sendfile, readfd, writefd, max_write);
186                 result.update_take(reader);
187
188                 match result {
189                     CopyResult::Ended(bytes_copied) => return Ok(bytes_copied + written),
190                     CopyResult::Error(e, _) => return Err(e),
191                     CopyResult::Fallback(bytes) => written += bytes,
192                 }
193             }
194
195             if input_meta.maybe_fifo() || output_meta.maybe_fifo() {
196                 let result = sendfile_splice(SpliceMode::Splice, readfd, writefd, max_write);
197                 result.update_take(reader);
198
199                 match result {
200                     CopyResult::Ended(bytes_copied) => return Ok(bytes_copied + written),
201                     CopyResult::Error(e, _) => return Err(e),
202                     CopyResult::Fallback(0) => { /* use the fallback below */ }
203                     CopyResult::Fallback(_) => {
204                         unreachable!("splice should not return > 0 bytes on the fallback path")
205                     }
206                 }
207             }
208         }
209
210         // fallback if none of the more specialized syscalls wants to work with these file descriptors
211         match generic_copy(reader, writer) {
212             Ok(bytes) => Ok(bytes + written),
213             err => err,
214         }
215     }
216 }
217
218 #[rustc_specialization_trait]
219 trait CopyRead: Read {
220     /// Implementations that contain buffers (i.e. `BufReader`) must transfer data from their internal
221     /// buffers into `writer` until either the buffers are emptied or `limit` bytes have been
222     /// transferred, whichever occurs sooner.
223     /// If nested buffers are present the outer buffers must be drained first.
224     ///
225     /// This is necessary to directly bypass the wrapper types while preserving the data order
226     /// when operating directly on the underlying file descriptors.
227     fn drain_to<W: Write>(&mut self, _writer: &mut W, _limit: u64) -> Result<u64> {
228         Ok(0)
229     }
230
231     /// Updates `Take` wrappers to remove the number of bytes copied.
232     fn taken(&mut self, _bytes: u64) {}
233
234     /// The minimum of the limit of all `Take<_>` wrappers, `u64::MAX` otherwise.
235     /// This method does not account for data `BufReader` buffers and would underreport
236     /// the limit of a `Take<BufReader<Take<_>>>` type. Thus its result is only valid
237     /// after draining the buffers via `drain_to`.
238     fn min_limit(&self) -> u64 {
239         u64::MAX
240     }
241
242     /// Extracts the file descriptor and hints/metadata, delegating through wrappers if necessary.
243     fn properties(&self) -> CopyParams;
244 }
245
246 #[rustc_specialization_trait]
247 trait CopyWrite: Write {
248     /// Extracts the file descriptor and hints/metadata, delegating through wrappers if necessary.
249     fn properties(&self) -> CopyParams;
250 }
251
252 impl<T> CopyRead for &mut T
253 where
254     T: CopyRead,
255 {
256     fn drain_to<W: Write>(&mut self, writer: &mut W, limit: u64) -> Result<u64> {
257         (**self).drain_to(writer, limit)
258     }
259
260     fn taken(&mut self, bytes: u64) {
261         (**self).taken(bytes);
262     }
263
264     fn min_limit(&self) -> u64 {
265         (**self).min_limit()
266     }
267
268     fn properties(&self) -> CopyParams {
269         (**self).properties()
270     }
271 }
272
273 impl<T> CopyWrite for &mut T
274 where
275     T: CopyWrite,
276 {
277     fn properties(&self) -> CopyParams {
278         (**self).properties()
279     }
280 }
281
282 impl CopyRead for File {
283     fn properties(&self) -> CopyParams {
284         CopyParams(fd_to_meta(self), Some(self.as_raw_fd()))
285     }
286 }
287
288 impl CopyRead for &File {
289     fn properties(&self) -> CopyParams {
290         CopyParams(fd_to_meta(*self), Some(self.as_raw_fd()))
291     }
292 }
293
294 impl CopyWrite for File {
295     fn properties(&self) -> CopyParams {
296         CopyParams(FdMeta::NoneObtained, Some(self.as_raw_fd()))
297     }
298 }
299
300 impl CopyWrite for &File {
301     fn properties(&self) -> CopyParams {
302         CopyParams(FdMeta::NoneObtained, Some(self.as_raw_fd()))
303     }
304 }
305
306 impl CopyRead for TcpStream {
307     fn properties(&self) -> CopyParams {
308         // avoid the stat syscall since we can be fairly sure it's a socket
309         CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
310     }
311 }
312
313 impl CopyRead for &TcpStream {
314     fn properties(&self) -> CopyParams {
315         // avoid the stat syscall since we can be fairly sure it's a socket
316         CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
317     }
318 }
319
320 impl CopyWrite for TcpStream {
321     fn properties(&self) -> CopyParams {
322         // avoid the stat syscall since we can be fairly sure it's a socket
323         CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
324     }
325 }
326
327 impl CopyWrite for &TcpStream {
328     fn properties(&self) -> CopyParams {
329         // avoid the stat syscall since we can be fairly sure it's a socket
330         CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
331     }
332 }
333
334 impl CopyRead for UnixStream {
335     fn properties(&self) -> CopyParams {
336         // avoid the stat syscall since we can be fairly sure it's a socket
337         CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
338     }
339 }
340
341 impl CopyRead for &UnixStream {
342     fn properties(&self) -> CopyParams {
343         // avoid the stat syscall since we can be fairly sure it's a socket
344         CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
345     }
346 }
347
348 impl CopyWrite for UnixStream {
349     fn properties(&self) -> CopyParams {
350         // avoid the stat syscall since we can be fairly sure it's a socket
351         CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
352     }
353 }
354
355 impl CopyWrite for &UnixStream {
356     fn properties(&self) -> CopyParams {
357         // avoid the stat syscall since we can be fairly sure it's a socket
358         CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
359     }
360 }
361
362 impl CopyWrite for ChildStdin {
363     fn properties(&self) -> CopyParams {
364         CopyParams(FdMeta::Pipe, Some(self.as_raw_fd()))
365     }
366 }
367
368 impl CopyRead for ChildStdout {
369     fn properties(&self) -> CopyParams {
370         CopyParams(FdMeta::Pipe, Some(self.as_raw_fd()))
371     }
372 }
373
374 impl CopyRead for ChildStderr {
375     fn properties(&self) -> CopyParams {
376         CopyParams(FdMeta::Pipe, Some(self.as_raw_fd()))
377     }
378 }
379
380 impl CopyRead for StdinLock<'_> {
381     fn drain_to<W: Write>(&mut self, writer: &mut W, outer_limit: u64) -> Result<u64> {
382         let buf_reader = self.as_mut_buf();
383         let buf = buf_reader.buffer();
384         let buf = &buf[0..min(buf.len(), outer_limit.try_into().unwrap_or(usize::MAX))];
385         let bytes_drained = buf.len();
386         writer.write_all(buf)?;
387         buf_reader.consume(bytes_drained);
388
389         Ok(bytes_drained as u64)
390     }
391
392     fn properties(&self) -> CopyParams {
393         CopyParams(fd_to_meta(self), Some(self.as_raw_fd()))
394     }
395 }
396
397 impl CopyWrite for StdoutLock<'_> {
398     fn properties(&self) -> CopyParams {
399         CopyParams(FdMeta::NoneObtained, Some(self.as_raw_fd()))
400     }
401 }
402
403 impl CopyWrite for StderrLock<'_> {
404     fn properties(&self) -> CopyParams {
405         CopyParams(FdMeta::NoneObtained, Some(self.as_raw_fd()))
406     }
407 }
408
409 impl<T: CopyRead> CopyRead for Take<T> {
410     fn drain_to<W: Write>(&mut self, writer: &mut W, outer_limit: u64) -> Result<u64> {
411         let local_limit = self.limit();
412         let combined_limit = min(outer_limit, local_limit);
413         let bytes_drained = self.get_mut().drain_to(writer, combined_limit)?;
414         // update limit since read() was bypassed
415         self.set_limit(local_limit - bytes_drained);
416
417         Ok(bytes_drained)
418     }
419
420     fn taken(&mut self, bytes: u64) {
421         self.set_limit(self.limit() - bytes);
422         self.get_mut().taken(bytes);
423     }
424
425     fn min_limit(&self) -> u64 {
426         min(Take::limit(self), self.get_ref().min_limit())
427     }
428
429     fn properties(&self) -> CopyParams {
430         self.get_ref().properties()
431     }
432 }
433
434 impl<T: CopyRead> CopyRead for BufReader<T> {
435     fn drain_to<W: Write>(&mut self, writer: &mut W, outer_limit: u64) -> Result<u64> {
436         let buf = self.buffer();
437         let buf = &buf[0..min(buf.len(), outer_limit.try_into().unwrap_or(usize::MAX))];
438         let bytes = buf.len();
439         writer.write_all(buf)?;
440         self.consume(bytes);
441
442         let remaining = outer_limit - bytes as u64;
443
444         // in case of nested bufreaders we also need to drain the ones closer to the source
445         let inner_bytes = self.get_mut().drain_to(writer, remaining)?;
446
447         Ok(bytes as u64 + inner_bytes)
448     }
449
450     fn taken(&mut self, bytes: u64) {
451         self.get_mut().taken(bytes);
452     }
453
454     fn min_limit(&self) -> u64 {
455         self.get_ref().min_limit()
456     }
457
458     fn properties(&self) -> CopyParams {
459         self.get_ref().properties()
460     }
461 }
462
463 impl<T: CopyWrite> CopyWrite for BufWriter<T> {
464     fn properties(&self) -> CopyParams {
465         self.get_ref().properties()
466     }
467 }
468
469 fn fd_to_meta<T: AsRawFd>(fd: &T) -> FdMeta {
470     let fd = fd.as_raw_fd();
471     let file: ManuallyDrop<File> = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) });
472     match file.metadata() {
473         Ok(meta) => FdMeta::Metadata(meta),
474         Err(_) => FdMeta::NoneObtained,
475     }
476 }
477
478 pub(super) enum CopyResult {
479     Ended(u64),
480     Error(Error, u64),
481     Fallback(u64),
482 }
483
484 impl CopyResult {
485     fn update_take(&self, reader: &mut impl CopyRead) {
486         match *self {
487             CopyResult::Fallback(bytes)
488             | CopyResult::Ended(bytes)
489             | CopyResult::Error(_, bytes) => reader.taken(bytes),
490         }
491     }
492 }
493
494 /// linux-specific implementation that will attempt to use copy_file_range for copy offloading
495 /// as the name says, it only works on regular files
496 ///
497 /// Callers must handle fallback to a generic copy loop.
498 /// `Fallback` may indicate non-zero number of bytes already written
499 /// if one of the files' cursor +`max_len` would exceed u64::MAX (`EOVERFLOW`).
500 pub(super) fn copy_regular_files(reader: RawFd, writer: RawFd, max_len: u64) -> CopyResult {
501     use crate::cmp;
502
503     // Kernel prior to 4.5 don't have copy_file_range
504     // We store the availability in a global to avoid unnecessary syscalls
505     static HAS_COPY_FILE_RANGE: AtomicBool = AtomicBool::new(true);
506
507     syscall! {
508         fn copy_file_range(
509             fd_in: libc::c_int,
510             off_in: *mut libc::loff_t,
511             fd_out: libc::c_int,
512             off_out: *mut libc::loff_t,
513             len: libc::size_t,
514             flags: libc::c_uint
515         ) -> libc::ssize_t
516     }
517
518     let has_copy_file_range = HAS_COPY_FILE_RANGE.load(Ordering::Relaxed);
519     let mut written = 0u64;
520     while written < max_len {
521         let copy_result = if has_copy_file_range {
522             let bytes_to_copy = cmp::min(max_len - written, usize::MAX as u64);
523             // cap to 1GB chunks in case u64::MAX is passed as max_len and the file has a non-zero seek position
524             // this allows us to copy large chunks without hitting EOVERFLOW,
525             // unless someone sets a file offset close to u64::MAX - 1GB, in which case a fallback would be required
526             let bytes_to_copy = cmp::min(bytes_to_copy as usize, 0x4000_0000usize);
527             let copy_result = unsafe {
528                 // We actually don't have to adjust the offsets,
529                 // because copy_file_range adjusts the file offset automatically
530                 cvt(copy_file_range(
531                     reader,
532                     ptr::null_mut(),
533                     writer,
534                     ptr::null_mut(),
535                     bytes_to_copy,
536                     0,
537                 ))
538             };
539             if let Err(ref copy_err) = copy_result {
540                 match copy_err.raw_os_error() {
541                     Some(libc::ENOSYS | libc::EPERM | libc::EOPNOTSUPP) => {
542                         HAS_COPY_FILE_RANGE.store(false, Ordering::Relaxed);
543                     }
544                     _ => {}
545                 }
546             }
547             copy_result
548         } else {
549             Err(Error::from_raw_os_error(libc::ENOSYS))
550         };
551         match copy_result {
552             Ok(0) if written == 0 => {
553                 // fallback to work around several kernel bugs where copy_file_range will fail to
554                 // copy any bytes and return 0 instead of an error if
555                 // - reading virtual files from the proc filesystem which appear to have 0 size
556                 //   but are not empty. noted in coreutils to affect kernels at least up to 5.6.19.
557                 // - copying from an overlay filesystem in docker. reported to occur on fedora 32.
558                 return CopyResult::Fallback(0);
559             }
560             Ok(0) => return CopyResult::Ended(written), // reached EOF
561             Ok(ret) => written += ret as u64,
562             Err(err) => {
563                 return match err.raw_os_error() {
564                     // when file offset + max_length > u64::MAX
565                     Some(libc::EOVERFLOW) => CopyResult::Fallback(written),
566                     Some(
567                         libc::ENOSYS | libc::EXDEV | libc::EINVAL | libc::EPERM | libc::EOPNOTSUPP,
568                     ) => {
569                         // Try fallback io::copy if either:
570                         // - Kernel version is < 4.5 (ENOSYS)
571                         // - Files are mounted on different fs (EXDEV)
572                         // - copy_file_range is broken in various ways on RHEL/CentOS 7 (EOPNOTSUPP)
573                         // - copy_file_range is disallowed, for example by seccomp (EPERM)
574                         // - copy_file_range cannot be used with pipes or device nodes (EINVAL)
575                         assert_eq!(written, 0);
576                         CopyResult::Fallback(0)
577                     }
578                     _ => CopyResult::Error(err, written),
579                 };
580             }
581         }
582     }
583     CopyResult::Ended(written)
584 }
585
586 #[derive(PartialEq)]
587 enum SpliceMode {
588     Sendfile,
589     Splice,
590 }
591
592 /// performs splice or sendfile between file descriptors
593 /// Does _not_ fall back to a generic copy loop.
594 fn sendfile_splice(mode: SpliceMode, reader: RawFd, writer: RawFd, len: u64) -> CopyResult {
595     static HAS_SENDFILE: AtomicBool = AtomicBool::new(true);
596     static HAS_SPLICE: AtomicBool = AtomicBool::new(true);
597
598     syscall! {
599         fn splice(
600             srcfd: libc::c_int,
601             src_offset: *const i64,
602             dstfd: libc::c_int,
603             dst_offset: *const i64,
604             len: libc::size_t,
605             flags: libc::c_int
606         ) -> libc::ssize_t
607     }
608
609     match mode {
610         SpliceMode::Sendfile if !HAS_SENDFILE.load(Ordering::Relaxed) => {
611             return CopyResult::Fallback(0);
612         }
613         SpliceMode::Splice if !HAS_SPLICE.load(Ordering::Relaxed) => {
614             return CopyResult::Fallback(0);
615         }
616         _ => (),
617     }
618
619     let mut written = 0u64;
620     while written < len {
621         // according to its manpage that's the maximum size sendfile() will copy per invocation
622         let chunk_size = crate::cmp::min(len - written, 0x7ffff000_u64) as usize;
623
624         let result = match mode {
625             SpliceMode::Sendfile => {
626                 cvt(unsafe { libc::sendfile(writer, reader, ptr::null_mut(), chunk_size) })
627             }
628             SpliceMode::Splice => cvt(unsafe {
629                 splice(reader, ptr::null_mut(), writer, ptr::null_mut(), chunk_size, 0)
630             }),
631         };
632
633         match result {
634             Ok(0) => break, // EOF
635             Ok(ret) => written += ret as u64,
636             Err(err) => {
637                 return match err.raw_os_error() {
638                     Some(libc::ENOSYS | libc::EPERM) => {
639                         // syscall not supported (ENOSYS)
640                         // syscall is disallowed, e.g. by seccomp (EPERM)
641                         match mode {
642                             SpliceMode::Sendfile => HAS_SENDFILE.store(false, Ordering::Relaxed),
643                             SpliceMode::Splice => HAS_SPLICE.store(false, Ordering::Relaxed),
644                         }
645                         assert_eq!(written, 0);
646                         CopyResult::Fallback(0)
647                     }
648                     Some(libc::EINVAL) => {
649                         // splice/sendfile do not support this particular file descriptor (EINVAL)
650                         assert_eq!(written, 0);
651                         CopyResult::Fallback(0)
652                     }
653                     Some(os_err) if mode == SpliceMode::Sendfile && os_err == libc::EOVERFLOW => {
654                         CopyResult::Fallback(written)
655                     }
656                     _ => CopyResult::Error(err, written),
657                 };
658             }
659         }
660     }
661     CopyResult::Ended(written)
662 }