@@ -143,22 +143,67 @@ impl MultiWriter {
143143 /// Copies all bytes from the input buffer to the output buffer
144144 /// without buffering which is POSIX requirement.
145145 pub fn copy_unbuffered ( & mut self ) -> Result < ( ) , ( ) > {
146- // todo: support splice() and tee() fast-path at here
147146 #[ cfg( not( any( unix, target_os = "wasi" ) ) ) ]
148147 use io:: Read as _;
149148 const BUF_SIZE : usize = 32 * 1024 ;
149+ #[ cfg( any( unix, target_os = "wasi" ) ) ]
150+ let input = rustix:: stdio:: stdin ( ) ;
151+ #[ cfg( not( any( unix, target_os = "wasi" ) ) ) ]
152+ let mut input = io:: stdin ( ) ;
153+ #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
154+ macro_rules! splice_or_detach {
155+ ( $pipe: expr, $writer: expr, $len: expr) => {
156+ if let Err ( e) = uucore:: pipes:: drain_pipe( $pipe, $writer, $len) {
157+ self . aborted |=
158+ process_error( self . output_error_mode, e, $writer, & mut self . ignored_errors)
159+ . is_err( ) ;
160+ $writer. name. clear( ) ; //mark as exited
161+ }
162+ } ;
163+ }
164+ // needs 2 pipes to duplicate input multiple times
165+ #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
166+ if let Ok ( ( pipe_read, pipe_write) ) = io:: pipe ( )
167+ && let Ok ( ( pipe2_read, pipe2_write) ) = io:: pipe ( )
168+ {
169+ use rustix:: pipe:: fcntl_setpipe_size;
170+ use uucore:: pipes:: MAX_ROOTLESS_PIPE_SIZE ;
171+ // improve throughput. 2nd pipe should be larger than 1st one for proper tee() length.
172+ if fcntl_setpipe_size ( & pipe2_read, MAX_ROOTLESS_PIPE_SIZE ) . is_ok ( ) {
173+ let _ = fcntl_setpipe_size ( & pipe_read, MAX_ROOTLESS_PIPE_SIZE ) ;
174+ let _ = fcntl_setpipe_size ( & self . writers [ 0 ] , MAX_ROOTLESS_PIPE_SIZE ) ; // stdout
175+ }
176+ while let Ok ( s) = uucore:: pipes:: splice ( & input, & pipe_write, MAX_ROOTLESS_PIPE_SIZE ) {
177+ if s == 0 {
178+ return Ok ( ( ) ) ;
179+ }
180+ let Some ( ( last, others) ) = self . writers . split_last_mut ( ) else {
181+ // all writers exited
182+ return Ok ( ( ) ) ;
183+ } ;
184+ for other in others {
185+ // do not consume input
186+ let tee_res = uucore:: pipes:: tee ( & pipe_read, & pipe2_write, s) ;
187+ assert_eq ! ( tee_res, Ok ( s) , "2nd pipe should have enough spare" ) ;
188+ splice_or_detach ! ( & pipe2_read, other, s) ;
189+ }
190+ // last one consumes input
191+ splice_or_detach ! ( & pipe_read, last, s) ;
192+ self . writers . retain ( |w| !w. name . is_empty ( ) ) ;
193+ if self . aborted {
194+ return Err ( ( ) ) ;
195+ }
196+ }
197+ }
198+
150199 #[ cfg( any( unix, target_os = "wasi" ) ) ]
151200 let mut buf = [ std:: mem:: MaybeUninit :: < u8 > :: uninit ( ) ; BUF_SIZE ] ;
152201 // todo: avoid cost by 0-fill keeping throughput
153202 #[ cfg( not( any( unix, target_os = "wasi" ) ) ) ]
154203 let mut buf = [ 0u8 ; BUF_SIZE ] ;
155-
156- let input = io:: stdin ( ) ;
157- #[ cfg( not( any( unix, target_os = "wasi" ) ) ) ]
158- let mut input = input;
159204 loop {
160205 #[ cfg( any( unix, target_os = "wasi" ) ) ]
161- let res = rustix:: io:: read ( & input, & mut buf)
206+ let res = rustix:: io:: read ( input, & mut buf)
162207 . map ( |f| f. 0 )
163208 . map_err ( Error :: from) ;
164209 #[ cfg( not( any( unix, target_os = "wasi" ) ) ) ]
@@ -267,3 +312,13 @@ struct NamedWriter {
267312 inner : Writer ,
268313 pub name : OsString ,
269314}
315+
316+ #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
317+ impl rustix:: fd:: AsFd for NamedWriter {
318+ fn as_fd ( & self ) -> rustix:: fd:: BorrowedFd < ' _ > {
319+ match & self . inner {
320+ Writer :: File ( f) => f. as_fd ( ) ,
321+ Writer :: Stdout ( s) => s. 0 ,
322+ }
323+ }
324+ }
0 commit comments