@@ -99,6 +99,7 @@ mod kernel_copy {
99
99
use crate :: os:: unix:: fs:: FileTypeExt ;
100
100
use crate :: os:: unix:: io:: { AsRawFd , FromRawFd , RawFd } ;
101
101
use crate :: process:: { ChildStderr , ChildStdin , ChildStdout } ;
102
+ use crate :: sys:: fs:: { copy_regular_files, sendfile_splice, CopyResult , SpliceMode } ;
102
103
103
104
pub ( super ) fn copy_spec < R : Read + ?Sized , W : Write + ?Sized > (
104
105
read : & mut R ,
@@ -108,20 +109,55 @@ mod kernel_copy {
108
109
SpecCopy :: copy ( copier)
109
110
}
110
111
112
+ /// This type represents either the inferred `FileType` of a `RawFd` based on the source
113
+ /// type from which it was extracted or the actual metadata
114
+ ///
115
+ /// The methods on this type only provide hints, due to `AsRawFd` and `FromRawFd` the inferred
116
+ /// type may be wrong.
111
117
enum FdMeta {
118
+ /// We obtained the FD from a type that can contain any type of `FileType` and queried the metadata
119
+ /// because it is cheaper than probing all possible syscalls (reader side)
112
120
Metadata ( Metadata ) ,
113
121
Socket ,
114
122
Pipe ,
115
- None ,
123
+ /// We don't have any metadata, e.g. because the original type was `File` which can represent
124
+ /// any `FileType` and we did not query the metadata either since it did not seem beneficial
125
+ /// (writer side)
126
+ NoneObtained ,
116
127
}
117
128
118
129
impl FdMeta {
119
- fn is_fifo ( & self ) -> bool {
130
+ fn maybe_fifo ( & self ) -> bool {
120
131
match self {
121
132
FdMeta :: Metadata ( meta) => meta. file_type ( ) . is_fifo ( ) ,
122
133
FdMeta :: Socket => false ,
123
134
FdMeta :: Pipe => true ,
124
- FdMeta :: None => false ,
135
+ FdMeta :: NoneObtained => true ,
136
+ }
137
+ }
138
+
139
+ fn potential_sendfile_source ( & self ) -> bool {
140
+ match self {
141
+ // procfs erronously shows 0 length on non-empty readable files.
142
+ // and if a file is truly empty then a `read` syscall will determine that and skip the write syscall
143
+ // thus there would be benefit from attempting sendfile
144
+ FdMeta :: Metadata ( meta)
145
+ if meta. file_type ( ) . is_file ( ) && meta. len ( ) > 0
146
+ || meta. file_type ( ) . is_block_device ( ) =>
147
+ {
148
+ true
149
+ }
150
+ _ => false ,
151
+ }
152
+ }
153
+
154
+ fn copy_file_range_candidate ( & self ) -> bool {
155
+ match self {
156
+ // copy_file_range will fail on empty procfs files. `read` can determine whether EOF has been reached
157
+ // without extra cost and skip the write, thus there is no benefit in attempting copy_file_range
158
+ FdMeta :: Metadata ( meta) if meta. is_file ( ) && meta. len ( ) > 0 => true ,
159
+ FdMeta :: NoneObtained => true ,
160
+ _ => false ,
125
161
}
126
162
}
127
163
}
@@ -149,66 +185,65 @@ mod kernel_copy {
149
185
let r_cfg = reader. properties ( ) ;
150
186
let w_cfg = writer. properties ( ) ;
151
187
152
- // before direct operations on file descriptors ensure that all source and sink buffers are emtpy
188
+ // before direct operations on file descriptors ensure that all source and sink buffers are emtpy
153
189
let mut flush = || -> crate :: io:: Result < u64 > {
154
190
let bytes = reader. drain_to ( writer, u64:: MAX ) ?;
191
+ // BufWriter buffered bytes have already been accounted for in earlier write() calls
155
192
writer. flush ( ) ?;
156
193
Ok ( bytes)
157
194
} ;
158
195
159
- match ( r_cfg, w_cfg) {
160
- (
161
- CopyParams ( FdMeta :: Metadata ( reader_meta) , Some ( readfd) ) ,
162
- CopyParams ( FdMeta :: Metadata ( writer_meta) , Some ( writefd) ) ,
163
- ) if reader_meta. is_file ( ) && writer_meta. is_file ( ) => {
164
- let bytes_flushed = flush ( ) ?;
165
- let max_write = reader. min_limit ( ) ;
166
- let ( mut reader, mut writer) =
167
- unsafe { ( fd_as_file ( readfd) , fd_as_file ( writefd) ) } ;
168
- let len = reader_meta. len ( ) ;
169
- crate :: sys:: fs:: copy_regular_files (
170
- & mut reader,
171
- & mut writer,
172
- min ( len, max_write) ,
173
- )
174
- . map ( |bytes_copied| bytes_copied + bytes_flushed)
196
+ let mut written = 0u64 ;
197
+
198
+ if let ( CopyParams ( input_meta, Some ( readfd) ) , CopyParams ( output_meta, Some ( writefd) ) ) =
199
+ ( r_cfg, w_cfg)
200
+ {
201
+ written += flush ( ) ?;
202
+ let max_write = reader. min_limit ( ) ;
203
+
204
+ if input_meta. copy_file_range_candidate ( ) && output_meta. copy_file_range_candidate ( )
205
+ {
206
+ let result = copy_regular_files ( readfd, writefd, max_write) ;
207
+
208
+ match result {
209
+ CopyResult :: Ended ( Ok ( bytes_copied) ) => return Ok ( bytes_copied + written) ,
210
+ CopyResult :: Ended ( err) => return err,
211
+ CopyResult :: Fallback ( bytes) => written += bytes,
212
+ }
175
213
}
176
- (
177
- CopyParams ( FdMeta :: Metadata ( reader_meta) , Some ( readfd) ) ,
178
- CopyParams ( _, Some ( writefd) ) ,
179
- ) if reader_meta. is_file ( ) => {
180
- // try sendfile, most modern systems it should work with any target as long as the source is a mmapable file.
181
- // in the rare cases where it's no supported the wrapper function will fall back to a normal copy loop
182
- let bytes_flushed = flush ( ) ?;
183
- let ( mut reader, mut writer) =
184
- unsafe { ( fd_as_file ( readfd) , fd_as_file ( writefd) ) } ;
185
- let len = reader_meta. len ( ) ;
186
- let max_write = reader. min_limit ( ) ;
187
- crate :: sys:: fs:: sendfile_splice (
188
- crate :: sys:: fs:: SpliceMode :: Sendfile ,
189
- & mut reader,
190
- & mut writer,
191
- min ( len, max_write) ,
192
- )
193
- . map ( |bytes_sent| bytes_sent + bytes_flushed)
214
+
215
+ // on modern kernels sendfile can copy from any mmapable type (some but not all regular files and block devices)
216
+ // to any writable file descriptor. On older kernels the writer side can only be a socket.
217
+ // So we just try and fallback if needed.
218
+ // If current file offsets + write sizes overflow it may also fail, we do not try to fix that and instead
219
+ // fall back to the generic copy loop.
220
+ if input_meta. potential_sendfile_source ( ) {
221
+ let result = sendfile_splice ( SpliceMode :: Sendfile , readfd, writefd, max_write) ;
222
+
223
+ match result {
224
+ CopyResult :: Ended ( Ok ( bytes_copied) ) => return Ok ( bytes_copied + written) ,
225
+ CopyResult :: Ended ( err) => return err,
226
+ CopyResult :: Fallback ( bytes) => written += bytes,
227
+ }
194
228
}
195
- ( CopyParams ( reader_meta, Some ( readfd) ) , CopyParams ( writer_meta, Some ( writefd) ) )
196
- if reader_meta. is_fifo ( ) || writer_meta. is_fifo ( ) =>
197
- {
198
- // splice
199
- let bytes_flushed = flush ( ) ?;
200
- let max_write = reader. min_limit ( ) ;
201
- let ( mut reader, mut writer) =
202
- unsafe { ( fd_as_file ( readfd) , fd_as_file ( writefd) ) } ;
203
- crate :: sys:: fs:: sendfile_splice (
204
- crate :: sys:: fs:: SpliceMode :: Splice ,
205
- & mut reader,
206
- & mut writer,
207
- max_write,
208
- )
209
- . map ( |bytes_sent| bytes_sent + bytes_flushed)
229
+
230
+ if input_meta. maybe_fifo ( ) || output_meta. maybe_fifo ( ) {
231
+ let result = sendfile_splice ( SpliceMode :: Splice , readfd, writefd, max_write) ;
232
+
233
+ match result {
234
+ CopyResult :: Ended ( Ok ( bytes_copied) ) => return Ok ( bytes_copied + written) ,
235
+ CopyResult :: Ended ( err) => return err,
236
+ CopyResult :: Fallback ( 0 ) => { /* use fallback */ }
237
+ CopyResult :: Fallback ( _) => {
238
+ unreachable ! ( "splice should not return > 0 bytes on the fallback path" )
239
+ }
240
+ }
210
241
}
211
- _ => super :: generic_copy ( reader, writer) ,
242
+ }
243
+
244
+ match super :: generic_copy ( reader, writer) {
245
+ Ok ( bytes) => Ok ( bytes + written) ,
246
+ err => err,
212
247
}
213
248
}
214
249
}
@@ -235,7 +270,10 @@ mod kernel_copy {
235
270
fn properties ( & self ) -> CopyParams ;
236
271
}
237
272
238
- impl < T > CopyRead for & mut T where T : CopyRead {
273
+ impl < T > CopyRead for & mut T
274
+ where
275
+ T : CopyRead ,
276
+ {
239
277
fn drain_to < W : Write > ( & mut self , writer : & mut W , limit : u64 ) -> Result < u64 > {
240
278
( * * self ) . drain_to ( writer, limit)
241
279
}
@@ -249,13 +287,15 @@ mod kernel_copy {
249
287
}
250
288
}
251
289
252
- impl < T > CopyWrite for & mut T where T : CopyWrite {
290
+ impl < T > CopyWrite for & mut T
291
+ where
292
+ T : CopyWrite ,
293
+ {
253
294
fn properties ( & self ) -> CopyParams {
254
295
( * * self ) . properties ( )
255
296
}
256
297
}
257
298
258
-
259
299
impl CopyRead for File {
260
300
fn properties ( & self ) -> CopyParams {
261
301
CopyParams ( fd_to_meta ( self ) , Some ( self . as_raw_fd ( ) ) )
@@ -270,13 +310,13 @@ mod kernel_copy {
270
310
271
311
impl CopyWrite for File {
272
312
fn properties ( & self ) -> CopyParams {
273
- CopyParams ( fd_to_meta ( self ) , Some ( self . as_raw_fd ( ) ) )
313
+ CopyParams ( FdMeta :: NoneObtained , Some ( self . as_raw_fd ( ) ) )
274
314
}
275
315
}
276
316
277
317
impl CopyWrite for & File {
278
318
fn properties ( & self ) -> CopyParams {
279
- CopyParams ( fd_to_meta ( * self ) , Some ( self . as_raw_fd ( ) ) )
319
+ CopyParams ( FdMeta :: NoneObtained , Some ( self . as_raw_fd ( ) ) )
280
320
}
281
321
}
282
322
@@ -345,13 +385,13 @@ mod kernel_copy {
345
385
346
386
impl CopyWrite for StdoutLock < ' _ > {
347
387
fn properties ( & self ) -> CopyParams {
348
- CopyParams ( fd_to_meta ( self ) , Some ( self . as_raw_fd ( ) ) )
388
+ CopyParams ( FdMeta :: NoneObtained , Some ( self . as_raw_fd ( ) ) )
349
389
}
350
390
}
351
391
352
392
impl CopyWrite for StderrLock < ' _ > {
353
393
fn properties ( & self ) -> CopyParams {
354
- CopyParams ( fd_to_meta ( self ) , Some ( self . as_raw_fd ( ) ) )
394
+ CopyParams ( FdMeta :: NoneObtained , Some ( self . as_raw_fd ( ) ) )
355
395
}
356
396
}
357
397
@@ -411,11 +451,7 @@ mod kernel_copy {
411
451
let file: ManuallyDrop < File > = ManuallyDrop :: new ( unsafe { File :: from_raw_fd ( fd) } ) ;
412
452
match file. metadata ( ) {
413
453
Ok ( meta) => FdMeta :: Metadata ( meta) ,
414
- Err ( _) => FdMeta :: None ,
454
+ Err ( _) => FdMeta :: NoneObtained ,
415
455
}
416
456
}
417
-
418
- unsafe fn fd_as_file ( fd : RawFd ) -> ManuallyDrop < File > {
419
- ManuallyDrop :: new ( File :: from_raw_fd ( fd) )
420
- }
421
457
}
0 commit comments