@@ -99,6 +99,7 @@ mod kernel_copy {
9999 use crate :: os:: unix:: fs:: FileTypeExt ;
100100 use crate :: os:: unix:: io:: { AsRawFd , FromRawFd , RawFd } ;
101101 use crate :: process:: { ChildStderr , ChildStdin , ChildStdout } ;
102+ use crate :: sys:: fs:: { copy_regular_files, sendfile_splice, CopyResult , SpliceMode } ;
102103
103104 pub ( super ) fn copy_spec < R : Read + ?Sized , W : Write + ?Sized > (
104105 read : & mut R ,
@@ -108,20 +109,55 @@ mod kernel_copy {
108109 SpecCopy :: copy ( copier)
109110 }
110111
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.
111117 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)
112120 Metadata ( Metadata ) ,
113121 Socket ,
114122 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 ,
116127 }
117128
118129 impl FdMeta {
119- fn is_fifo ( & self ) -> bool {
130+ fn maybe_fifo ( & self ) -> bool {
120131 match self {
121132 FdMeta :: Metadata ( meta) => meta. file_type ( ) . is_fifo ( ) ,
122133 FdMeta :: Socket => false ,
123134 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 ,
125161 }
126162 }
127163 }
@@ -149,66 +185,65 @@ mod kernel_copy {
149185 let r_cfg = reader. properties ( ) ;
150186 let w_cfg = writer. properties ( ) ;
151187
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
153189 let mut flush = || -> crate :: io:: Result < u64 > {
154190 let bytes = reader. drain_to ( writer, u64:: MAX ) ?;
191+ // BufWriter buffered bytes have already been accounted for in earlier write() calls
155192 writer. flush ( ) ?;
156193 Ok ( bytes)
157194 } ;
158195
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+ }
175213 }
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+ }
194228 }
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+ }
210241 }
211- _ => super :: generic_copy ( reader, writer) ,
242+ }
243+
244+ match super :: generic_copy ( reader, writer) {
245+ Ok ( bytes) => Ok ( bytes + written) ,
246+ err => err,
212247 }
213248 }
214249 }
@@ -235,7 +270,10 @@ mod kernel_copy {
235270 fn properties ( & self ) -> CopyParams ;
236271 }
237272
238- impl < T > CopyRead for & mut T where T : CopyRead {
273+ impl < T > CopyRead for & mut T
274+ where
275+ T : CopyRead ,
276+ {
239277 fn drain_to < W : Write > ( & mut self , writer : & mut W , limit : u64 ) -> Result < u64 > {
240278 ( * * self ) . drain_to ( writer, limit)
241279 }
@@ -249,13 +287,15 @@ mod kernel_copy {
249287 }
250288 }
251289
252- impl < T > CopyWrite for & mut T where T : CopyWrite {
290+ impl < T > CopyWrite for & mut T
291+ where
292+ T : CopyWrite ,
293+ {
253294 fn properties ( & self ) -> CopyParams {
254295 ( * * self ) . properties ( )
255296 }
256297 }
257298
258-
259299 impl CopyRead for File {
260300 fn properties ( & self ) -> CopyParams {
261301 CopyParams ( fd_to_meta ( self ) , Some ( self . as_raw_fd ( ) ) )
@@ -270,13 +310,13 @@ mod kernel_copy {
270310
271311 impl CopyWrite for File {
272312 fn properties ( & self ) -> CopyParams {
273- CopyParams ( fd_to_meta ( self ) , Some ( self . as_raw_fd ( ) ) )
313+ CopyParams ( FdMeta :: NoneObtained , Some ( self . as_raw_fd ( ) ) )
274314 }
275315 }
276316
277317 impl CopyWrite for & File {
278318 fn properties ( & self ) -> CopyParams {
279- CopyParams ( fd_to_meta ( * self ) , Some ( self . as_raw_fd ( ) ) )
319+ CopyParams ( FdMeta :: NoneObtained , Some ( self . as_raw_fd ( ) ) )
280320 }
281321 }
282322
@@ -345,13 +385,13 @@ mod kernel_copy {
345385
346386 impl CopyWrite for StdoutLock < ' _ > {
347387 fn properties ( & self ) -> CopyParams {
348- CopyParams ( fd_to_meta ( self ) , Some ( self . as_raw_fd ( ) ) )
388+ CopyParams ( FdMeta :: NoneObtained , Some ( self . as_raw_fd ( ) ) )
349389 }
350390 }
351391
352392 impl CopyWrite for StderrLock < ' _ > {
353393 fn properties ( & self ) -> CopyParams {
354- CopyParams ( fd_to_meta ( self ) , Some ( self . as_raw_fd ( ) ) )
394+ CopyParams ( FdMeta :: NoneObtained , Some ( self . as_raw_fd ( ) ) )
355395 }
356396 }
357397
@@ -411,11 +451,7 @@ mod kernel_copy {
411451 let file: ManuallyDrop < File > = ManuallyDrop :: new ( unsafe { File :: from_raw_fd ( fd) } ) ;
412452 match file. metadata ( ) {
413453 Ok ( meta) => FdMeta :: Metadata ( meta) ,
414- Err ( _) => FdMeta :: None ,
454+ Err ( _) => FdMeta :: NoneObtained ,
415455 }
416456 }
417-
418- unsafe fn fd_as_file ( fd : RawFd ) -> ManuallyDrop < File > {
419- ManuallyDrop :: new ( File :: from_raw_fd ( fd) )
420- }
421457}
0 commit comments