17
17
//! Once it has obtained all necessary pieces and brought any wrapper types into a state where they
18
18
//! can be safely bypassed it will attempt to use the `copy_file_range(2)`,
19
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 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.
25
23
//! Once a working syscall is found for a pair of file descriptors it will be called in a loop
26
24
//! until the copy operation is completed.
27
25
//!
@@ -84,14 +82,10 @@ pub(crate) fn copy_spec<R: Read + ?Sized, W: Write + ?Sized>(
84
82
/// The methods on this type only provide hints, due to `AsRawFd` and `FromRawFd` the inferred
85
83
/// type may be wrong.
86
84
enum 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)
89
85
Metadata ( Metadata ) ,
90
86
Socket ,
91
87
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
95
89
NoneObtained ,
96
90
}
97
91
@@ -131,6 +125,39 @@ impl FdMeta {
131
125
}
132
126
}
133
127
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
+
134
161
struct CopyParams ( FdMeta , Option < RawFd > ) ;
135
162
136
163
struct Copier < ' a , ' b , R : Read + ?Sized , W : Write + ?Sized > {
@@ -186,7 +213,8 @@ impl<R: CopyRead, W: CopyWrite> SpecCopy for Copier<'_, '_, R, W> {
186
213
// So we just try and fallback if needed.
187
214
// If current file offsets + write sizes overflow it may also fail, we do not try to fix that and instead
188
215
// 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
+ {
190
218
let result = sendfile_splice ( SpliceMode :: Sendfile , readfd, writefd, max_write) ;
191
219
result. update_take ( reader) ;
192
220
@@ -197,7 +225,9 @@ impl<R: CopyRead, W: CopyWrite> SpecCopy for Copier<'_, '_, R, W> {
197
225
}
198
226
}
199
227
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
+ {
201
231
let result = sendfile_splice ( SpliceMode :: Splice , readfd, writefd, max_write) ;
202
232
result. update_take ( reader) ;
203
233
@@ -298,13 +328,13 @@ impl CopyRead for &File {
298
328
299
329
impl CopyWrite for File {
300
330
fn properties ( & self ) -> CopyParams {
301
- CopyParams ( FdMeta :: NoneObtained , Some ( self . as_raw_fd ( ) ) )
331
+ CopyParams ( fd_to_meta ( self ) , Some ( self . as_raw_fd ( ) ) )
302
332
}
303
333
}
304
334
305
335
impl CopyWrite for & File {
306
336
fn properties ( & self ) -> CopyParams {
307
- CopyParams ( FdMeta :: NoneObtained , Some ( self . as_raw_fd ( ) ) )
337
+ CopyParams ( fd_to_meta ( * self ) , Some ( self . as_raw_fd ( ) ) )
308
338
}
309
339
}
310
340
@@ -401,13 +431,13 @@ impl CopyRead for StdinLock<'_> {
401
431
402
432
impl CopyWrite for StdoutLock < ' _ > {
403
433
fn properties ( & self ) -> CopyParams {
404
- CopyParams ( FdMeta :: NoneObtained , Some ( self . as_raw_fd ( ) ) )
434
+ CopyParams ( fd_to_meta ( self ) , Some ( self . as_raw_fd ( ) ) )
405
435
}
406
436
}
407
437
408
438
impl CopyWrite for StderrLock < ' _ > {
409
439
fn properties ( & self ) -> CopyParams {
410
- CopyParams ( FdMeta :: NoneObtained , Some ( self . as_raw_fd ( ) ) )
440
+ CopyParams ( fd_to_meta ( self ) , Some ( self . as_raw_fd ( ) ) )
411
441
}
412
442
}
413
443
0 commit comments