Skip to main content

std/sys/io/kernel_copy/
linux.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 it attempts
21//! to use them one after another (guided by hints) to figure out which one works and
22//! falls back to the generic read-write copy loop if none of them does.
23//! Once a working syscall is found for a pair of file descriptors it will be called in a loop
24//! until the copy operation is completed.
25//!
26//! Advantages of using these syscalls:
27//!
28//! * fewer context switches since reads and writes are coalesced into a single syscall
29//!   and more bytes are transferred per syscall. This translates to higher throughput
30//!   and fewer CPU cycles, at least for sufficiently large transfers to amortize the initial probing.
31//! * `copy_file_range` creates reflink copies on CoW filesystems, thus moving less data and
32//!   consuming less disk space
33//! * `sendfile` and `splice` can perform zero-copy IO under some circumstances while
34//!   a naive copy loop would move every byte through the CPU.
35//!
36//! Drawbacks:
37//!
38//! * copy operations smaller than the default buffer size can under some circumstances, especially
39//!   on older kernels, incur more syscalls than the naive approach would. As mentioned above
40//!   the syscall selection is guided by hints to minimize this possibility but they are not perfect.
41//! * optimizations only apply to std types. If a user adds a custom wrapper type, e.g. to report
42//!   progress, they can hit a performance cliff.
43//! * complexity
44
45#[cfg(not(any(all(target_os = "linux", target_env = "gnu"), target_os = "hurd")))]
46use libc::sendfile as sendfile64;
47#[cfg(any(all(target_os = "linux", target_env = "gnu"), target_os = "hurd"))]
48use libc::sendfile64;
49use libc::{EBADF, EINVAL, ENOSYS, EOPNOTSUPP, EOVERFLOW, EPERM, EXDEV};
50
51use crate::cmp::min;
52use crate::fs::{File, Metadata};
53use crate::io::{
54    self, BufRead, BufReader, BufWriter, CopyState, Error, PipeReader, PipeWriter, Read, Result,
55    StderrLock, StdinLock, StdoutLock, Take, Write,
56};
57use crate::mem::ManuallyDrop;
58use crate::net::TcpStream;
59use crate::os::unix::fs::FileTypeExt;
60use crate::os::unix::io::{AsRawFd, FromRawFd, RawFd};
61use crate::os::unix::net::UnixStream;
62use crate::process::{ChildStderr, ChildStdin, ChildStdout};
63use crate::ptr;
64use crate::sync::atomic::{Atomic, AtomicBool, AtomicU8, Ordering};
65use crate::sys::cvt;
66use crate::sys::fs::CachedFileMetadata;
67use crate::sys::weak::syscall;
68
69#[cfg(test)]
70mod tests;
71
72#[doc(hidden)]
73#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")]
74impl io::SpecCopy for File {
75    fn copy<R: Read + ?Sized, W: Write + ?Sized>(read: &mut R, write: &mut W) -> Result<CopyState> {
76        SpecCopy::copy(Copier { read, write })
77    }
78}
79
80#[doc(hidden)]
81#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")]
82impl io::SpecCopy for &File {
83    fn copy<R: Read + ?Sized, W: Write + ?Sized>(read: &mut R, write: &mut W) -> Result<CopyState> {
84        SpecCopy::copy(Copier { read, write })
85    }
86}
87
88#[doc(hidden)]
89#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")]
90impl io::SpecCopy for TcpStream {
91    fn copy<R: Read + ?Sized, W: Write + ?Sized>(read: &mut R, write: &mut W) -> Result<CopyState> {
92        SpecCopy::copy(Copier { read, write })
93    }
94}
95
96#[doc(hidden)]
97#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")]
98impl io::SpecCopy for &TcpStream {
99    fn copy<R: Read + ?Sized, W: Write + ?Sized>(read: &mut R, write: &mut W) -> Result<CopyState> {
100        SpecCopy::copy(Copier { read, write })
101    }
102}
103
104#[doc(hidden)]
105#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")]
106impl io::SpecCopy for UnixStream {
107    fn copy<R: Read + ?Sized, W: Write + ?Sized>(read: &mut R, write: &mut W) -> Result<CopyState> {
108        SpecCopy::copy(Copier { read, write })
109    }
110}
111
112#[doc(hidden)]
113#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")]
114impl io::SpecCopy for &UnixStream {
115    fn copy<R: Read + ?Sized, W: Write + ?Sized>(read: &mut R, write: &mut W) -> Result<CopyState> {
116        SpecCopy::copy(Copier { read, write })
117    }
118}
119
120#[doc(hidden)]
121#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")]
122impl io::SpecCopy for PipeReader {
123    fn copy<R: Read + ?Sized, W: Write + ?Sized>(read: &mut R, write: &mut W) -> Result<CopyState> {
124        SpecCopy::copy(Copier { read, write })
125    }
126}
127
128#[doc(hidden)]
129#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")]
130impl io::SpecCopy for &PipeReader {
131    fn copy<R: Read + ?Sized, W: Write + ?Sized>(read: &mut R, write: &mut W) -> Result<CopyState> {
132        SpecCopy::copy(Copier { read, write })
133    }
134}
135
136#[doc(hidden)]
137#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")]
138impl io::SpecCopy for ChildStdout {
139    fn copy<R: Read + ?Sized, W: Write + ?Sized>(read: &mut R, write: &mut W) -> Result<CopyState> {
140        SpecCopy::copy(Copier { read, write })
141    }
142}
143
144#[doc(hidden)]
145#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")]
146impl io::SpecCopy for ChildStderr {
147    fn copy<R: Read + ?Sized, W: Write + ?Sized>(read: &mut R, write: &mut W) -> Result<CopyState> {
148        SpecCopy::copy(Copier { read, write })
149    }
150}
151
152#[doc(hidden)]
153#[unstable(feature = "io_copy_internals", reason = "implementation detail", issue = "none")]
154impl io::SpecCopy for StdinLock<'_> {
155    fn copy<R: Read + ?Sized, W: Write + ?Sized>(read: &mut R, write: &mut W) -> Result<CopyState> {
156        SpecCopy::copy(Copier { read, write })
157    }
158}
159
160impl io::SpecCopy for CachedFileMetadata {
161    fn copy<R: Read + ?Sized, W: Write + ?Sized>(read: &mut R, write: &mut W) -> Result<CopyState> {
162        SpecCopy::copy(Copier { read, write })
163    }
164}
165
166/// This type represents either the inferred `FileType` of a `RawFd` based on the source
167/// type from which it was extracted or the actual metadata
168///
169/// The methods on this type only provide hints, due to `AsRawFd` and `FromRawFd` the inferred
170/// type may be wrong.
171enum FdMeta {
172    Metadata(Metadata),
173    Socket,
174    Pipe,
175    /// We don't have any metadata because the stat syscall failed
176    NoneObtained,
177}
178
179#[derive(#[automatically_derived]
impl ::core::marker::StructuralPartialEq for FdHandle { }
#[automatically_derived]
impl ::core::cmp::PartialEq for FdHandle {
    #[inline]
    fn eq(&self, other: &FdHandle) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq)]
180enum FdHandle {
181    Input,
182    Output,
183}
184
185impl FdMeta {
186    fn maybe_fifo(&self) -> bool {
187        match self {
188            FdMeta::Metadata(meta) => meta.file_type().is_fifo(),
189            FdMeta::Socket => false,
190            FdMeta::Pipe => true,
191            FdMeta::NoneObtained => true,
192        }
193    }
194
195    fn potential_sendfile_source(&self) -> bool {
196        match self {
197            // procfs erroneously shows 0 length on non-empty readable files.
198            // and if a file is truly empty then a `read` syscall will determine that and skip the write syscall
199            // thus there would be benefit from attempting sendfile
200            FdMeta::Metadata(meta)
201                if meta.file_type().is_file() && meta.len() > 0
202                    || meta.file_type().is_block_device() =>
203            {
204                true
205            }
206            _ => false,
207        }
208    }
209
210    fn copy_file_range_candidate(&self, f: FdHandle) -> bool {
211        match self {
212            // copy_file_range will fail on empty procfs files. `read` can determine whether EOF has been reached
213            // without extra cost and skip the write, thus there is no benefit in attempting copy_file_range
214            FdMeta::Metadata(meta) if f == FdHandle::Input && meta.is_file() && meta.len() > 0 => {
215                true
216            }
217            FdMeta::Metadata(meta) if f == FdHandle::Output && meta.is_file() => true,
218            _ => false,
219        }
220    }
221}
222
223/// Returns true either if changes made to the source after a sendfile/splice call won't become
224/// visible in the sink or the source has explicitly opted into such behavior (e.g. by splicing
225/// a file into a pipe, the pipe being the source in this case).
226///
227/// This will prevent File -> Pipe and File -> Socket splicing/sendfile optimizations to uphold
228/// the Read/Write API semantics of io::copy.
229///
230/// Note: This is not 100% airtight, the caller can use the RawFd conversion methods to turn a
231/// regular file into a TcpSocket which will be treated as a socket here without checking.
232fn safe_kernel_copy(source: &FdMeta, sink: &FdMeta) -> bool {
233    match (source, sink) {
234        // Data arriving from a socket is safe because the sender can't modify the socket buffer.
235        // Data arriving from a pipe is safe(-ish) because either the sender *copied*
236        // the bytes into the pipe OR explicitly performed an operation that enables zero-copy,
237        // thus promising not to modify the data later.
238        (FdMeta::Socket, _) => true,
239        (FdMeta::Pipe, _) => true,
240        (FdMeta::Metadata(meta), _)
241            if meta.file_type().is_fifo() || meta.file_type().is_socket() =>
242        {
243            true
244        }
245        // Data going into non-pipes/non-sockets is safe because the "later changes may become visible" issue
246        // only happens for pages sitting in send buffers or pipes.
247        (_, FdMeta::Metadata(meta))
248            if !meta.file_type().is_fifo() && !meta.file_type().is_socket() =>
249        {
250            true
251        }
252        _ => false,
253    }
254}
255
256struct CopyParams(FdMeta, Option<RawFd>);
257
258struct Copier<'a, 'b, R: Read + ?Sized, W: Write + ?Sized> {
259    read: &'a mut R,
260    write: &'b mut W,
261}
262
263trait SpecCopy {
264    fn copy(self) -> Result<CopyState>;
265}
266
267impl<R: Read + ?Sized, W: Write + ?Sized> SpecCopy for Copier<'_, '_, R, W> {
268    default fn copy(self) -> Result<CopyState> {
269        Ok(CopyState::Fallback(0))
270    }
271}
272
273impl<R: CopyRead, W: CopyWrite> SpecCopy for Copier<'_, '_, R, W> {
274    fn copy(self) -> Result<CopyState> {
275        let (reader, writer) = (self.read, self.write);
276        let r_cfg = reader.properties();
277        let w_cfg = writer.properties();
278
279        // before direct operations on file descriptors ensure that all source and sink buffers are empty
280        let mut flush = || -> Result<u64> {
281            let bytes = reader.drain_to(writer, u64::MAX)?;
282            // BufWriter buffered bytes have already been accounted for in earlier write() calls
283            writer.flush()?;
284            Ok(bytes)
285        };
286
287        let mut written = 0u64;
288
289        if let (CopyParams(input_meta, Some(readfd)), CopyParams(output_meta, Some(writefd))) =
290            (r_cfg, w_cfg)
291        {
292            written += flush()?;
293            let max_write = reader.min_limit();
294
295            if input_meta.copy_file_range_candidate(FdHandle::Input)
296                && output_meta.copy_file_range_candidate(FdHandle::Output)
297            {
298                let result = copy_regular_files(readfd, writefd, max_write);
299                result.update_take(reader);
300
301                match result {
302                    CopyResult::Ended(bytes_copied) => {
303                        return Ok(CopyState::Ended(bytes_copied + written));
304                    }
305                    CopyResult::Error(e, _) => return Err(e),
306                    CopyResult::Fallback(bytes) => written += bytes,
307                }
308            }
309
310            // on modern kernels sendfile can copy from any mmapable type (some but not all regular files and block devices)
311            // to any writable file descriptor. On older kernels the writer side can only be a socket.
312            // So we just try and fallback if needed.
313            // If current file offsets + write sizes overflow it may also fail, we do not try to fix that and instead
314            // fall back to the generic copy loop.
315            if input_meta.potential_sendfile_source() && safe_kernel_copy(&input_meta, &output_meta)
316            {
317                let result = sendfile_splice(SpliceMode::Sendfile, readfd, writefd, max_write);
318                result.update_take(reader);
319
320                match result {
321                    CopyResult::Ended(bytes_copied) => {
322                        return Ok(CopyState::Ended(bytes_copied + written));
323                    }
324                    CopyResult::Error(e, _) => return Err(e),
325                    CopyResult::Fallback(bytes) => written += bytes,
326                }
327            }
328
329            if (input_meta.maybe_fifo() || output_meta.maybe_fifo())
330                && safe_kernel_copy(&input_meta, &output_meta)
331            {
332                let result = sendfile_splice(SpliceMode::Splice, readfd, writefd, max_write);
333                result.update_take(reader);
334
335                match result {
336                    CopyResult::Ended(bytes_copied) => {
337                        return Ok(CopyState::Ended(bytes_copied + written));
338                    }
339                    CopyResult::Error(e, _) => return Err(e),
340                    CopyResult::Fallback(0) => { /* use the fallback below */ }
341                    CopyResult::Fallback(_) => {
342                        {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("splice should not return > 0 bytes on the fallback path")));
}unreachable!("splice should not return > 0 bytes on the fallback path")
343                    }
344                }
345            }
346        }
347
348        // fallback if none of the more specialized syscalls wants to work with these file descriptors
349        Ok(CopyState::Fallback(written))
350    }
351}
352
353#[rustc_specialization_trait]
354trait CopyRead: Read {
355    /// Implementations that contain buffers (i.e. `BufReader`) must transfer data from their internal
356    /// buffers into `writer` until either the buffers are emptied or `limit` bytes have been
357    /// transferred, whichever occurs sooner.
358    /// If nested buffers are present the outer buffers must be drained first.
359    ///
360    /// This is necessary to directly bypass the wrapper types while preserving the data order
361    /// when operating directly on the underlying file descriptors.
362    fn drain_to<W: Write>(&mut self, _writer: &mut W, _limit: u64) -> Result<u64> {
363        Ok(0)
364    }
365
366    /// Updates `Take` wrappers to remove the number of bytes copied.
367    fn taken(&mut self, _bytes: u64) {}
368
369    /// The minimum of the limit of all `Take<_>` wrappers, `u64::MAX` otherwise.
370    /// This method does not account for data `BufReader` buffers and would underreport
371    /// the limit of a `Take<BufReader<Take<_>>>` type. Thus its result is only valid
372    /// after draining the buffers via `drain_to`.
373    fn min_limit(&self) -> u64 {
374        u64::MAX
375    }
376
377    /// Extracts the file descriptor and hints/metadata, delegating through wrappers if necessary.
378    fn properties(&self) -> CopyParams;
379}
380
381#[rustc_specialization_trait]
382trait CopyWrite: Write {
383    /// Extracts the file descriptor and hints/metadata, delegating through wrappers if necessary.
384    fn properties(&self) -> CopyParams;
385}
386
387impl<T> CopyRead for &mut T
388where
389    T: CopyRead,
390{
391    fn drain_to<W: Write>(&mut self, writer: &mut W, limit: u64) -> Result<u64> {
392        (**self).drain_to(writer, limit)
393    }
394
395    fn taken(&mut self, bytes: u64) {
396        (**self).taken(bytes);
397    }
398
399    fn min_limit(&self) -> u64 {
400        (**self).min_limit()
401    }
402
403    fn properties(&self) -> CopyParams {
404        (**self).properties()
405    }
406}
407
408impl<T> CopyWrite for &mut T
409where
410    T: CopyWrite,
411{
412    fn properties(&self) -> CopyParams {
413        (**self).properties()
414    }
415}
416
417impl CopyRead for File {
418    fn properties(&self) -> CopyParams {
419        CopyParams(fd_to_meta(self), Some(self.as_raw_fd()))
420    }
421}
422
423impl CopyRead for &File {
424    fn properties(&self) -> CopyParams {
425        CopyParams(fd_to_meta(*self), Some(self.as_raw_fd()))
426    }
427}
428
429impl CopyWrite for File {
430    fn properties(&self) -> CopyParams {
431        CopyParams(fd_to_meta(self), Some(self.as_raw_fd()))
432    }
433}
434
435impl CopyWrite for &File {
436    fn properties(&self) -> CopyParams {
437        CopyParams(fd_to_meta(*self), Some(self.as_raw_fd()))
438    }
439}
440
441impl CopyRead for TcpStream {
442    fn properties(&self) -> CopyParams {
443        // avoid the stat syscall since we can be fairly sure it's a socket
444        CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
445    }
446}
447
448impl CopyRead for &TcpStream {
449    fn properties(&self) -> CopyParams {
450        // avoid the stat syscall since we can be fairly sure it's a socket
451        CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
452    }
453}
454
455impl CopyWrite for TcpStream {
456    fn properties(&self) -> CopyParams {
457        // avoid the stat syscall since we can be fairly sure it's a socket
458        CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
459    }
460}
461
462impl CopyWrite for &TcpStream {
463    fn properties(&self) -> CopyParams {
464        // avoid the stat syscall since we can be fairly sure it's a socket
465        CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
466    }
467}
468
469impl CopyRead for UnixStream {
470    fn properties(&self) -> CopyParams {
471        // avoid the stat syscall since we can be fairly sure it's a socket
472        CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
473    }
474}
475
476impl CopyRead for &UnixStream {
477    fn properties(&self) -> CopyParams {
478        // avoid the stat syscall since we can be fairly sure it's a socket
479        CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
480    }
481}
482
483impl CopyWrite for UnixStream {
484    fn properties(&self) -> CopyParams {
485        // avoid the stat syscall since we can be fairly sure it's a socket
486        CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
487    }
488}
489
490impl CopyWrite for &UnixStream {
491    fn properties(&self) -> CopyParams {
492        // avoid the stat syscall since we can be fairly sure it's a socket
493        CopyParams(FdMeta::Socket, Some(self.as_raw_fd()))
494    }
495}
496
497impl CopyRead for PipeReader {
498    fn properties(&self) -> CopyParams {
499        CopyParams(FdMeta::Pipe, Some(self.as_raw_fd()))
500    }
501}
502
503impl CopyRead for &PipeReader {
504    fn properties(&self) -> CopyParams {
505        CopyParams(FdMeta::Pipe, Some(self.as_raw_fd()))
506    }
507}
508
509impl CopyWrite for PipeWriter {
510    fn properties(&self) -> CopyParams {
511        CopyParams(FdMeta::Pipe, Some(self.as_raw_fd()))
512    }
513}
514
515impl CopyWrite for &PipeWriter {
516    fn properties(&self) -> CopyParams {
517        CopyParams(FdMeta::Pipe, Some(self.as_raw_fd()))
518    }
519}
520
521impl CopyWrite for ChildStdin {
522    fn properties(&self) -> CopyParams {
523        CopyParams(FdMeta::Pipe, Some(self.as_raw_fd()))
524    }
525}
526
527impl CopyRead for ChildStdout {
528    fn properties(&self) -> CopyParams {
529        CopyParams(FdMeta::Pipe, Some(self.as_raw_fd()))
530    }
531}
532
533impl CopyRead for ChildStderr {
534    fn properties(&self) -> CopyParams {
535        CopyParams(FdMeta::Pipe, Some(self.as_raw_fd()))
536    }
537}
538
539impl CopyRead for StdinLock<'_> {
540    fn drain_to<W: Write>(&mut self, writer: &mut W, outer_limit: u64) -> Result<u64> {
541        let buf_reader = self.as_mut_buf();
542        let buf = buf_reader.buffer();
543        let buf = &buf[0..min(buf.len(), outer_limit.try_into().unwrap_or(usize::MAX))];
544        let bytes_drained = buf.len();
545        writer.write_all(buf)?;
546        buf_reader.consume(bytes_drained);
547
548        Ok(bytes_drained as u64)
549    }
550
551    fn properties(&self) -> CopyParams {
552        CopyParams(fd_to_meta(self), Some(self.as_raw_fd()))
553    }
554}
555
556impl CopyWrite for StdoutLock<'_> {
557    fn properties(&self) -> CopyParams {
558        CopyParams(fd_to_meta(self), Some(self.as_raw_fd()))
559    }
560}
561
562impl CopyWrite for StderrLock<'_> {
563    fn properties(&self) -> CopyParams {
564        CopyParams(fd_to_meta(self), Some(self.as_raw_fd()))
565    }
566}
567
568impl<T: CopyRead> CopyRead for Take<T> {
569    fn drain_to<W: Write>(&mut self, writer: &mut W, outer_limit: u64) -> Result<u64> {
570        let local_limit = self.limit();
571        let combined_limit = min(outer_limit, local_limit);
572        let bytes_drained = self.get_mut().drain_to(writer, combined_limit)?;
573        // update limit since read() was bypassed
574        self.set_limit(local_limit - bytes_drained);
575
576        Ok(bytes_drained)
577    }
578
579    fn taken(&mut self, bytes: u64) {
580        self.set_limit(self.limit() - bytes);
581        self.get_mut().taken(bytes);
582    }
583
584    fn min_limit(&self) -> u64 {
585        min(Take::limit(self), self.get_ref().min_limit())
586    }
587
588    fn properties(&self) -> CopyParams {
589        self.get_ref().properties()
590    }
591}
592
593impl<T: ?Sized + CopyRead> CopyRead for BufReader<T> {
594    fn drain_to<W: Write>(&mut self, writer: &mut W, outer_limit: u64) -> Result<u64> {
595        let buf = self.buffer();
596        let buf = &buf[0..min(buf.len(), outer_limit.try_into().unwrap_or(usize::MAX))];
597        let bytes = buf.len();
598        writer.write_all(buf)?;
599        self.consume(bytes);
600
601        let remaining = outer_limit - bytes as u64;
602
603        // in case of nested bufreaders we also need to drain the ones closer to the source
604        let inner_bytes = self.get_mut().drain_to(writer, remaining)?;
605
606        Ok(bytes as u64 + inner_bytes)
607    }
608
609    fn taken(&mut self, bytes: u64) {
610        self.get_mut().taken(bytes);
611    }
612
613    fn min_limit(&self) -> u64 {
614        self.get_ref().min_limit()
615    }
616
617    fn properties(&self) -> CopyParams {
618        self.get_ref().properties()
619    }
620}
621
622impl<T: ?Sized + CopyWrite> CopyWrite for BufWriter<T> {
623    fn properties(&self) -> CopyParams {
624        self.get_ref().properties()
625    }
626}
627
628impl CopyRead for CachedFileMetadata {
629    fn properties(&self) -> CopyParams {
630        CopyParams(FdMeta::Metadata(self.1.clone()), Some(self.0.as_raw_fd()))
631    }
632}
633
634impl CopyWrite for CachedFileMetadata {
635    fn properties(&self) -> CopyParams {
636        CopyParams(FdMeta::Metadata(self.1.clone()), Some(self.0.as_raw_fd()))
637    }
638}
639
640fn fd_to_meta<T: AsRawFd>(fd: &T) -> FdMeta {
641    let fd = fd.as_raw_fd();
642    let file: ManuallyDrop<File> = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) });
643    match file.metadata() {
644        Ok(meta) => FdMeta::Metadata(meta),
645        Err(_) => FdMeta::NoneObtained,
646    }
647}
648
649enum CopyResult {
650    Ended(u64),
651    Error(Error, u64),
652    Fallback(u64),
653}
654
655impl CopyResult {
656    fn update_take(&self, reader: &mut impl CopyRead) {
657        match *self {
658            CopyResult::Fallback(bytes)
659            | CopyResult::Ended(bytes)
660            | CopyResult::Error(_, bytes) => reader.taken(bytes),
661        }
662    }
663}
664
665/// Invalid file descriptor.
666///
667/// Valid file descriptors are guaranteed to be positive numbers (see `open()` manpage)
668/// while negative values are used to indicate errors.
669/// Thus -1 will never be overlap with a valid open file.
670const INVALID_FD: RawFd = -1;
671
672/// Linux-specific implementation that will attempt to use copy_file_range for copy offloading.
673/// As the name says, it only works on regular files.
674///
675/// Callers must handle fallback to a generic copy loop.
676/// `Fallback` may indicate non-zero number of bytes already written
677/// if one of the files' cursor +`max_len` would exceed u64::MAX (`EOVERFLOW`).
678fn copy_regular_files(reader: RawFd, writer: RawFd, max_len: u64) -> CopyResult {
679    use crate::cmp;
680
681    const NOT_PROBED: u8 = 0;
682    const UNAVAILABLE: u8 = 1;
683    const AVAILABLE: u8 = 2;
684
685    // Kernel prior to 4.5 don't have copy_file_range
686    // We store the availability in a global to avoid unnecessary syscalls
687    static HAS_COPY_FILE_RANGE: Atomic<u8> = AtomicU8::new(NOT_PROBED);
688
689    let mut have_probed = match HAS_COPY_FILE_RANGE.load(Ordering::Relaxed) {
690        NOT_PROBED => false,
691        UNAVAILABLE => return CopyResult::Fallback(0),
692        _ => true,
693    };
694
695    unsafe fn copy_file_range(fd_in: libc::c_int, off_in: *mut libc::loff_t,
    fd_out: libc::c_int, off_out: *mut libc::loff_t, len: libc::size_t,
    flags: libc::c_uint) -> libc::ssize_t {
    let ref copy_file_range:
            ExternWeak<unsafe extern "C" fn(libc::c_int, *mut libc::loff_t,
                libc::c_int, *mut libc::loff_t, libc::size_t, libc::c_uint)
                -> libc::ssize_t> =
        {
            unsafe extern "C" {
                #[linkage = "extern_weak"]
                static copy_file_range:
                    Option<unsafe extern "C" fn(libc::c_int, *mut libc::loff_t,
                        libc::c_int, *mut libc::loff_t, libc::size_t, libc::c_uint)
                        -> libc::ssize_t>;
            }

            #[allow(unused_unsafe)]
            ExternWeak::new(unsafe { copy_file_range })
        };
    if let Some(fun) = copy_file_range.get() {
        unsafe { fun(fd_in, off_in, fd_out, off_out, len, flags) }
    } else {
        unsafe {
            libc::syscall(libc::SYS_copy_file_range, fd_in, off_in, fd_out,
                    off_out, len, flags) as libc::ssize_t
        }
    }
}syscall!(
696        fn copy_file_range(
697            fd_in: libc::c_int,
698            off_in: *mut libc::loff_t,
699            fd_out: libc::c_int,
700            off_out: *mut libc::loff_t,
701            len: libc::size_t,
702            flags: libc::c_uint,
703        ) -> libc::ssize_t;
704    );
705
706    fn probe_copy_file_range_support() -> u8 {
707        // In some cases, we cannot determine availability from the first
708        // `copy_file_range` call. In this case, we probe with an invalid file
709        // descriptor so that the results are easily interpretable.
710        match unsafe {
711            cvt(copy_file_range(INVALID_FD, ptr::null_mut(), INVALID_FD, ptr::null_mut(), 1, 0))
712                .map_err(|e| e.raw_os_error())
713        } {
714            Err(Some(EPERM | ENOSYS)) => UNAVAILABLE,
715            Err(Some(EBADF)) => AVAILABLE,
716            Ok(_) => {
    ::core::panicking::panic_fmt(format_args!("unexpected copy_file_range probe success"));
}panic!("unexpected copy_file_range probe success"),
717            // Treat other errors as the syscall
718            // being unavailable.
719            Err(_) => UNAVAILABLE,
720        }
721    }
722
723    let mut written = 0u64;
724    while written < max_len {
725        let bytes_to_copy = cmp::min(max_len - written, usize::MAX as u64);
726        // cap to 1GB chunks in case u64::MAX is passed as max_len and the file has a non-zero seek position
727        // this allows us to copy large chunks without hitting EOVERFLOW,
728        // unless someone sets a file offset close to u64::MAX - 1GB, in which case a fallback would be required
729        let bytes_to_copy = cmp::min(bytes_to_copy as usize, 0x4000_0000usize);
730        let copy_result = unsafe {
731            // We actually don't have to adjust the offsets,
732            // because copy_file_range adjusts the file offset automatically
733            cvt(copy_file_range(reader, ptr::null_mut(), writer, ptr::null_mut(), bytes_to_copy, 0))
734        };
735
736        if !have_probed && copy_result.is_ok() {
737            have_probed = true;
738            HAS_COPY_FILE_RANGE.store(AVAILABLE, Ordering::Relaxed);
739        }
740
741        match copy_result {
742            Ok(0) if written == 0 => {
743                // fallback to work around several kernel bugs where copy_file_range will fail to
744                // copy any bytes and return 0 instead of an error if
745                // - reading virtual files from the proc filesystem which appear to have 0 size
746                //   but are not empty. noted in coreutils to affect kernels at least up to 5.6.19.
747                // - copying from an overlay filesystem in docker. reported to occur on fedora 32.
748                return CopyResult::Fallback(0);
749            }
750            Ok(0) => return CopyResult::Ended(written), // reached EOF
751            Ok(ret) => written += ret as u64,
752            Err(err) => {
753                return match err.raw_os_error() {
754                    // when file offset + max_length > u64::MAX
755                    Some(EOVERFLOW) => CopyResult::Fallback(written),
756                    Some(raw_os_error @ (ENOSYS | EXDEV | EINVAL | EPERM | EOPNOTSUPP | EBADF))
757                        if written == 0 =>
758                    {
759                        if !have_probed {
760                            let available = if #[allow(non_exhaustive_omitted_patterns)] match raw_os_error {
    ENOSYS | EOPNOTSUPP | EPERM => true,
    _ => false,
}matches!(raw_os_error, ENOSYS | EOPNOTSUPP | EPERM) {
761                                // EPERM can indicate seccomp filters or an
762                                // immutable file. To distinguish these
763                                // cases we probe with invalid file
764                                // descriptors which should result in EBADF
765                                // if the syscall is supported and EPERM or
766                                // ENOSYS if it's not available.
767                                //
768                                // For EOPNOTSUPP, see below. In the case of
769                                // ENOSYS, we try to cover for faulty FUSE
770                                // drivers.
771                                probe_copy_file_range_support()
772                            } else {
773                                AVAILABLE
774                            };
775                            HAS_COPY_FILE_RANGE.store(available, Ordering::Relaxed);
776                        }
777
778                        // Try fallback io::copy if either:
779                        // - Kernel version is < 4.5 (ENOSYS¹)
780                        // - Files are mounted on different fs (EXDEV)
781                        // - copy_file_range is broken in various ways on RHEL/CentOS 7 (EOPNOTSUPP)
782                        // - copy_file_range file is immutable or syscall is blocked by seccomp¹ (EPERM)
783                        // - copy_file_range cannot be used with pipes or device nodes (EINVAL)
784                        // - the writer fd was opened with O_APPEND (EBADF²)
785                        // and no bytes were written successfully yet. (All these errnos should
786                        // not be returned if something was already written, but they happen in
787                        // the wild, see #91152.)
788                        //
789                        // ¹ these cases should be detected by the initial probe but we handle them here
790                        //   anyway in case syscall interception changes during runtime
791                        // ² actually invalid file descriptors would cause this too, but in that case
792                        //   the fallback code path is expected to encounter the same error again
793                        CopyResult::Fallback(0)
794                    }
795                    _ => CopyResult::Error(err, written),
796                };
797            }
798        }
799    }
800    CopyResult::Ended(written)
801}
802
803#[derive(#[automatically_derived]
impl ::core::marker::StructuralPartialEq for SpliceMode { }
#[automatically_derived]
impl ::core::cmp::PartialEq for SpliceMode {
    #[inline]
    fn eq(&self, other: &SpliceMode) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq)]
804enum SpliceMode {
805    Sendfile,
806    Splice,
807}
808
809/// performs splice or sendfile between file descriptors
810/// Does _not_ fall back to a generic copy loop.
811fn sendfile_splice(mode: SpliceMode, reader: RawFd, writer: RawFd, len: u64) -> CopyResult {
812    static HAS_SENDFILE: Atomic<bool> = AtomicBool::new(true);
813    static HAS_SPLICE: Atomic<bool> = AtomicBool::new(true);
814
815    // Android builds use feature level 14, but the libc wrapper for splice is
816    // gated on feature level 21+, so we have to invoke the syscall directly.
817    #[cfg(target_os = "android")]
818    syscall!(
819        fn splice(
820            srcfd: libc::c_int,
821            src_offset: *const i64,
822            dstfd: libc::c_int,
823            dst_offset: *const i64,
824            len: libc::size_t,
825            flags: libc::c_int,
826        ) -> libc::ssize_t;
827    );
828
829    #[cfg(target_os = "linux")]
830    use libc::splice;
831
832    match mode {
833        SpliceMode::Sendfile if !HAS_SENDFILE.load(Ordering::Relaxed) => {
834            return CopyResult::Fallback(0);
835        }
836        SpliceMode::Splice if !HAS_SPLICE.load(Ordering::Relaxed) => {
837            return CopyResult::Fallback(0);
838        }
839        _ => (),
840    }
841
842    let mut written = 0u64;
843    while written < len {
844        // according to its manpage that's the maximum size sendfile() will copy per invocation
845        let chunk_size = crate::cmp::min(len - written, 0x7ffff000_u64) as usize;
846
847        let result = match mode {
848            SpliceMode::Sendfile => {
849                cvt(unsafe { sendfile64(writer, reader, ptr::null_mut(), chunk_size) })
850            }
851            SpliceMode::Splice => cvt(unsafe {
852                splice(reader, ptr::null_mut(), writer, ptr::null_mut(), chunk_size, 0)
853            }),
854        };
855
856        match result {
857            Ok(0) => break, // EOF
858            Ok(ret) => written += ret as u64,
859            Err(err) => {
860                return match err.raw_os_error() {
861                    Some(ENOSYS | EPERM) => {
862                        // syscall not supported (ENOSYS)
863                        // syscall is disallowed, e.g. by seccomp (EPERM)
864                        match mode {
865                            SpliceMode::Sendfile => HAS_SENDFILE.store(false, Ordering::Relaxed),
866                            SpliceMode::Splice => HAS_SPLICE.store(false, Ordering::Relaxed),
867                        }
868                        {
    match (&written, &0) {
        (left_val, right_val) => {
            if !(*left_val == *right_val) {
                let kind = ::core::panicking::AssertKind::Eq;
                ::core::panicking::assert_failed(kind, &*left_val,
                    &*right_val, ::core::option::Option::None);
            }
        }
    }
};assert_eq!(written, 0);
869                        CopyResult::Fallback(0)
870                    }
871                    Some(EINVAL) => {
872                        // splice/sendfile do not support this particular file descriptor (EINVAL)
873                        {
    match (&written, &0) {
        (left_val, right_val) => {
            if !(*left_val == *right_val) {
                let kind = ::core::panicking::AssertKind::Eq;
                ::core::panicking::assert_failed(kind, &*left_val,
                    &*right_val, ::core::option::Option::None);
            }
        }
    }
};assert_eq!(written, 0);
874                        CopyResult::Fallback(0)
875                    }
876                    Some(os_err) if mode == SpliceMode::Sendfile && os_err == EOVERFLOW => {
877                        CopyResult::Fallback(written)
878                    }
879                    _ => CopyResult::Error(err, written),
880                };
881            }
882        }
883    }
884    CopyResult::Ended(written)
885}