1717//! Once it has obtained all necessary pieces and brought any wrapper types into a state where they
1818//! can be safely bypassed it will attempt to use the `copy_file_range(2)`,
1919//! `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 falls back to the generic read-write copy loop if none of them
24- //! does.
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.
2523//! Once a working syscall is found for a pair of file descriptors it will be called in a loop
2624//! until the copy operation is completed.
2725//!
@@ -84,14 +82,10 @@ pub(crate) fn copy_spec<R: Read + ?Sized, W: Write + ?Sized>(
8482/// The methods on this type only provide hints, due to `AsRawFd` and `FromRawFd` the inferred
8583/// type may be wrong.
8684enum FdMeta {
87- /// We obtained the FD from a type that can contain any type of `FileType` and queried the metadata
88- /// because it is cheaper than probing all possible syscalls (reader side)
8985 Metadata ( Metadata ) ,
9086 Socket ,
9187 Pipe ,
92- /// We don't have any metadata, e.g. because the original type was `File` which can represent
93- /// any `FileType` and we did not query the metadata either since it did not seem beneficial
94- /// (writer side)
88+ /// We don't have any metadata because the stat syscall failed
9589 NoneObtained ,
9690}
9791
@@ -131,6 +125,39 @@ impl FdMeta {
131125 }
132126}
133127
128+ /// Returns true either if changes made to the source after a sendfile/splice call won't become
129+ /// visible in the sink or the source has explicitly opted into such behavior (e.g. by splicing
130+ /// a file into a pipe, the pipe being the source in this case).
131+ ///
132+ /// This will prevent File -> Pipe and File -> Socket splicing/sendfile optimizations to uphold
133+ /// the Read/Write API semantics of io::copy.
134+ ///
135+ /// Note: This is not 100% airtight, the caller can use the RawFd conversion methods to turn a
136+ /// regular file into a TcpSocket which will be treated as a socket here without checking.
137+ fn safe_kernel_copy ( source : & FdMeta , sink : & FdMeta ) -> bool {
138+ match ( source, sink) {
139+ // Data arriving from a socket is safe because the sender can't modify the socket buffer.
140+ // Data arriving from a pipe is safe(-ish) because either the sender *copied*
141+ // the bytes into the pipe OR explicitly performed an operation that enables zero-copy,
142+ // thus promising not to modify the data later.
143+ ( FdMeta :: Socket , _) => true ,
144+ ( FdMeta :: Pipe , _) => true ,
145+ ( FdMeta :: Metadata ( meta) , _)
146+ if meta. file_type ( ) . is_fifo ( ) || meta. file_type ( ) . is_socket ( ) =>
147+ {
148+ true
149+ }
150+ // Data going into non-pipes/non-sockets is safe because the "later changes may become visible" issue
151+ // only happens for pages sitting in send buffers or pipes.
152+ ( _, FdMeta :: Metadata ( meta) )
153+ if !meta. file_type ( ) . is_fifo ( ) && !meta. file_type ( ) . is_socket ( ) =>
154+ {
155+ true
156+ }
157+ _ => false ,
158+ }
159+ }
160+
134161struct CopyParams ( FdMeta , Option < RawFd > ) ;
135162
136163struct Copier < ' a , ' b , R : Read + ?Sized , W : Write + ?Sized > {
@@ -186,7 +213,8 @@ impl<R: CopyRead, W: CopyWrite> SpecCopy for Copier<'_, '_, R, W> {
186213 // So we just try and fallback if needed.
187214 // If current file offsets + write sizes overflow it may also fail, we do not try to fix that and instead
188215 // fall back to the generic copy loop.
189- if input_meta. potential_sendfile_source ( ) {
216+ if input_meta. potential_sendfile_source ( ) && safe_kernel_copy ( & input_meta, & output_meta)
217+ {
190218 let result = sendfile_splice ( SpliceMode :: Sendfile , readfd, writefd, max_write) ;
191219 result. update_take ( reader) ;
192220
@@ -197,7 +225,9 @@ impl<R: CopyRead, W: CopyWrite> SpecCopy for Copier<'_, '_, R, W> {
197225 }
198226 }
199227
200- if input_meta. maybe_fifo ( ) || output_meta. maybe_fifo ( ) {
228+ if ( input_meta. maybe_fifo ( ) || output_meta. maybe_fifo ( ) )
229+ && safe_kernel_copy ( & input_meta, & output_meta)
230+ {
201231 let result = sendfile_splice ( SpliceMode :: Splice , readfd, writefd, max_write) ;
202232 result. update_take ( reader) ;
203233
@@ -298,13 +328,13 @@ impl CopyRead for &File {
298328
299329impl CopyWrite for File {
300330 fn properties ( & self ) -> CopyParams {
301- CopyParams ( FdMeta :: NoneObtained , Some ( self . as_raw_fd ( ) ) )
331+ CopyParams ( fd_to_meta ( self ) , Some ( self . as_raw_fd ( ) ) )
302332 }
303333}
304334
305335impl CopyWrite for & File {
306336 fn properties ( & self ) -> CopyParams {
307- CopyParams ( FdMeta :: NoneObtained , Some ( self . as_raw_fd ( ) ) )
337+ CopyParams ( fd_to_meta ( * self ) , Some ( self . as_raw_fd ( ) ) )
308338 }
309339}
310340
@@ -401,13 +431,13 @@ impl CopyRead for StdinLock<'_> {
401431
402432impl CopyWrite for StdoutLock < ' _ > {
403433 fn properties ( & self ) -> CopyParams {
404- CopyParams ( FdMeta :: NoneObtained , Some ( self . as_raw_fd ( ) ) )
434+ CopyParams ( fd_to_meta ( self ) , Some ( self . as_raw_fd ( ) ) )
405435 }
406436}
407437
408438impl CopyWrite for StderrLock < ' _ > {
409439 fn properties ( & self ) -> CopyParams {
410- CopyParams ( FdMeta :: NoneObtained , Some ( self . as_raw_fd ( ) ) )
440+ CopyParams ( fd_to_meta ( self ) , Some ( self . as_raw_fd ( ) ) )
411441 }
412442}
413443
0 commit comments