@@ -12,7 +12,14 @@ use uucore::error::{UResult, USimpleError, strip_errno};
1212use uucore:: format_usage;
1313use uucore:: translate;
1414
15+ #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
16+ const PAGE_SIZE : usize = 4096 ;
17+ #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
18+ use uucore:: pipes:: MAX_ROOTLESS_PIPE_SIZE ;
19+ #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
20+ const BUF_SIZE : usize = MAX_ROOTLESS_PIPE_SIZE ;
1521// it's possible that using a smaller or larger buffer might provide better performance
22+ #[ cfg( not( any( target_os = "linux" , target_os = "android" ) ) ) ]
1623const BUF_SIZE : usize = 16 * 1024 ;
1724
1825#[ uucore:: main]
@@ -21,9 +28,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
2128
2229 #[ allow( clippy:: unwrap_used, reason = "clap provides 'y' by default" ) ]
2330 let mut buffer = args_into_buffer ( matches. get_many :: < OsString > ( "STRING" ) . unwrap ( ) ) ?;
24- prepare_buffer ( & mut buffer) ;
31+ #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
32+ let aligned_before_growing = PAGE_SIZE . is_multiple_of ( buffer. len ( ) ) ;
2533
26- match exec ( & buffer) {
34+ prepare_buffer ( & mut buffer) ;
35+ #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
36+ let res = exec ( & buffer, aligned_before_growing) ;
37+ #[ cfg( not( any( target_os = "linux" , target_os = "android" ) ) ) ]
38+ let res = exec ( & buffer) ;
39+ match res {
2740 Ok ( ( ) ) => Ok ( ( ) ) ,
2841 // On Windows, silently handle broken pipe since there's no SIGPIPE
2942 #[ cfg( windows) ]
@@ -97,10 +110,44 @@ fn prepare_buffer(buf: &mut Vec<u8>) {
97110 }
98111}
99112
113+ #[ cfg( not( any( target_os = "linux" , target_os = "android" ) ) ) ]
100114pub fn exec ( bytes : & [ u8 ] ) -> io:: Result < ( ) > {
101- let stdout = io:: stdout ( ) ;
102- let mut stdout = stdout. lock ( ) ;
115+ let mut stdout = io:: stdout ( ) . lock ( ) ;
116+ loop {
117+ stdout. write_all ( bytes) ?;
118+ }
119+ }
103120
121+ #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
122+ pub fn exec ( bytes : & [ u8 ] , aligned : bool ) -> io:: Result < ( ) > {
123+ use uucore:: pipes:: { pipe, splice, tee} ;
124+ let mut stdout = io:: stdout ( ) ; // no need to lock with zero-copy
125+ // don't show any error from fast-path and fallback to write for proper message
126+ if let Ok ( ( p_read, mut p_write) ) = pipe ( )
127+ // todo: zero-copy with default size when fcntl failed
128+ && rustix:: pipe:: fcntl_setpipe_size ( & stdout, MAX_ROOTLESS_PIPE_SIZE ) . is_ok ( )
129+ && p_write. write_all ( bytes) . is_ok ( )
130+ {
131+ if aligned && tee ( & p_read, & stdout, PAGE_SIZE ) . is_ok ( ) {
132+ while let Ok ( 1 ..) = tee ( & p_read, & stdout, usize:: MAX ) { }
133+ } else if let Ok ( ( broker_read, broker_write) ) = pipe ( ) {
134+ // tee() cannot control offset and write to non-pipe
135+ ' hybrid: while let Ok ( mut remain) = tee ( & p_read, & broker_write, usize:: MAX ) {
136+ debug_assert ! ( remain == bytes. len( ) , "splice() should cleanup pipe" ) ;
137+ while remain > 0 {
138+ if let Ok ( s) = splice ( & broker_read, & stdout, remain) {
139+ remain -= s;
140+ } else {
141+ // avoid output breakage with reduced remain even if it would not happen
142+ stdout. write_all ( & bytes[ bytes. len ( ) - remain..] ) ?;
143+ break ' hybrid;
144+ }
145+ }
146+ }
147+ }
148+ }
149+ // fallback
150+ let mut stdout = stdout. lock ( ) ;
104151 loop {
105152 stdout. write_all ( bytes) ?;
106153 }
@@ -111,6 +158,7 @@ mod tests {
111158 use super :: * ;
112159
113160 #[ test]
161+ #[ cfg( not( any( target_os = "linux" , target_os = "android" ) ) ) ] // Linux uses different buffer size
114162 fn test_prepare_buffer ( ) {
115163 let tests = [
116164 ( 150 , 16350 ) ,
0 commit comments