33// For the full copyright and license information, please view the LICENSE
44// file that was distributed with this source code.
55
6- // cSpell:ignore strs
6+ // cSpell:ignore strs setpipe
77
8+ use aligned_vec:: { AVec , Alignment , ConstAlign } ;
89use clap:: { Arg , ArgAction , Command , builder:: ValueParser } ;
910use std:: error:: Error ;
1011use std:: ffi:: OsString ;
@@ -13,18 +14,34 @@ use uucore::error::{UResult, USimpleError};
1314use uucore:: format_usage;
1415use uucore:: translate;
1516
17+ #[ cfg( target_os = "linux" ) ]
18+ use rustix:: {
19+ fd:: { AsRawFd , BorrowedFd } ,
20+ param:: page_size,
21+ pipe:: { IoSliceRaw , SpliceFlags , fcntl_setpipe_size, vmsplice} ,
22+ } ;
23+
24+ // Should be multiple of page size
25+ const ROOTLESS_MAX_PIPE_SIZE : usize = 1024 * 1024 ;
1626// it's possible that using a smaller or larger buffer might provide better performance on some
1727// systems, but honestly this is good enough
1828const BUF_SIZE : usize = 16 * 1024 ;
1929
2030#[ uucore:: main]
2131pub fn uumain ( args : impl uucore:: Args ) -> UResult < ( ) > {
2232 let matches = uucore:: clap_localization:: handle_clap_result ( uu_app ( ) , args) ?;
23-
24- let mut buffer = Vec :: with_capacity ( BUF_SIZE ) ;
33+ // use larger pipe if zero-copy is possible
34+ // todo: deduplicate logic
35+ // increase pipe size if stdout is pipe for zero-copy performance
36+ #[ cfg( target_os = "linux" ) ]
37+ let buf_size = fcntl_setpipe_size ( & io:: stdout ( ) , ROOTLESS_MAX_PIPE_SIZE ) . unwrap_or ( BUF_SIZE ) ;
38+ #[ cfg( not( target_os = "linux" ) ) ]
39+ let buf_size = BUF_SIZE ;
40+ let mut buffer: AVec < u8 , ConstAlign < ROOTLESS_MAX_PIPE_SIZE > > =
41+ AVec :: with_capacity ( ROOTLESS_MAX_PIPE_SIZE , buf_size) ;
2542 #[ allow( clippy:: unwrap_used, reason = "clap provides 'y' by default" ) ]
2643 let _ = args_into_buffer ( & mut buffer, matches. get_many :: < OsString > ( "STRING" ) . unwrap ( ) ) ;
27- prepare_buffer ( & mut buffer) ;
44+ prepare_buffer ( & mut buffer, buf_size ) ;
2845
2946 match exec ( & buffer) {
3047 Ok ( ( ) ) => Ok ( ( ) ) ,
@@ -55,8 +72,8 @@ pub fn uu_app() -> Command {
5572
5673/// Copies words from `i` into `buf`, separated by spaces.
5774#[ allow( clippy:: unnecessary_wraps, reason = "needed on some platforms" ) ]
58- fn args_into_buffer < ' a > (
59- buf : & mut Vec < u8 > ,
75+ fn args_into_buffer < ' a , A : Alignment > (
76+ buf : & mut AVec < u8 , A > ,
6077 i : impl Iterator < Item = & ' a OsString > ,
6178) -> Result < ( ) , Box < dyn Error > > {
6279 // On Unix (and wasi), OsStrs are just &[u8]'s underneath...
@@ -91,23 +108,29 @@ fn args_into_buffer<'a>(
91108
92109/// Assumes buf holds a single output line forged from the command line arguments, copies it
93110/// repeatedly until the buffer holds as many copies as it can under [`BUF_SIZE`].
94- fn prepare_buffer ( buf : & mut Vec < u8 > ) {
95- if buf. len ( ) * 2 > BUF_SIZE {
111+ fn prepare_buffer < A : Alignment > ( buf : & mut AVec < u8 , A > , buf_size : usize ) {
112+ if buf. len ( ) * 2 > buf_size {
96113 return ;
97114 }
98115
99116 assert ! ( !buf. is_empty( ) ) ;
100117
101118 let line_len = buf. len ( ) ;
102- let target_size = line_len * ( BUF_SIZE / line_len) ;
119+ let target_size = line_len * ( buf_size / line_len) ;
103120
104121 while buf. len ( ) < target_size {
105- let to_copy = std:: cmp:: min ( target_size - buf. len ( ) , buf. len ( ) ) ;
122+ let current_len = buf. len ( ) ;
123+ let to_copy = std:: cmp:: min ( target_size - current_len, current_len) ;
106124 debug_assert_eq ! ( to_copy % line_len, 0 ) ;
107- buf. extend_from_within ( ..to_copy) ;
125+ #[ allow(
126+ clippy:: unnecessary_to_owned,
127+ reason = "needs useless copy without unsafe"
128+ ) ]
129+ buf. extend_from_slice ( & buf[ ..to_copy] . to_vec ( ) ) ;
108130 }
109131}
110132
133+ #[ cfg( not( target_os = "linux" ) ) ]
111134pub fn exec ( bytes : & [ u8 ] ) -> io:: Result < ( ) > {
112135 let stdout = io:: stdout ( ) ;
113136 let mut stdout = stdout. lock ( ) ;
@@ -117,6 +140,25 @@ pub fn exec(bytes: &[u8]) -> io::Result<()> {
117140 }
118141}
119142
143+ #[ cfg( target_os = "linux" ) ]
144+ pub fn exec ( bytes : & [ u8 ] ) -> io:: Result < ( ) > {
145+ let stdout = io:: stdout ( ) ;
146+ //zero copy fast-path
147+ //needed for large args
148+ //todo: align instead of giving up fast-path
149+ let aligned = bytes. len ( ) . is_multiple_of ( page_size ( ) ) ;
150+ if aligned {
151+ let fd_raw = stdout. as_raw_fd ( ) ;
152+ let fd = unsafe { BorrowedFd :: borrow_raw ( fd_raw) } ;
153+ let iovec = [ IoSliceRaw :: from_slice ( bytes) ] ;
154+ while unsafe { vmsplice ( fd, & iovec, SpliceFlags :: empty ( ) ) } . is_ok ( ) { }
155+ }
156+ let mut stdout = stdout. lock ( ) ;
157+ loop {
158+ stdout. write_all ( bytes) ?;
159+ }
160+ }
161+
120162#[ cfg( test) ]
121163mod tests {
122164 use super :: * ;
@@ -142,33 +184,40 @@ mod tests {
142184 ] ;
143185
144186 for ( line, final_len) in tests {
145- let mut v = std:: iter:: repeat_n ( b'a' , line) . collect :: < Vec < _ > > ( ) ;
146- prepare_buffer ( & mut v) ;
187+ let mut v: AVec < u8 , ConstAlign < ROOTLESS_MAX_PIPE_SIZE > > =
188+ AVec :: from_iter ( ROOTLESS_MAX_PIPE_SIZE , std:: iter:: repeat_n ( b'a' , line) ) ;
189+ prepare_buffer ( & mut v, BUF_SIZE ) ;
147190 assert_eq ! ( v. len( ) , final_len) ;
148191 }
149192 }
150193
151194 #[ test]
152195 fn test_args_into_buf ( ) {
153196 {
154- let mut v = Vec :: with_capacity ( BUF_SIZE ) ;
197+ let mut v: AVec < u8 , ConstAlign < ROOTLESS_MAX_PIPE_SIZE > > =
198+ AVec :: with_capacity ( ROOTLESS_MAX_PIPE_SIZE , BUF_SIZE ) ;
155199 let default_args = [ "y" . into ( ) ] ;
156200 args_into_buffer ( & mut v, default_args. iter ( ) ) . unwrap ( ) ;
157- assert_eq ! ( String :: from_utf8( v) . unwrap( ) , "y\n " ) ;
201+ assert_eq ! ( String :: from_utf8( v. to_vec ( ) ) . unwrap( ) , "y\n " ) ;
158202 }
159203
160204 {
161- let mut v = Vec :: with_capacity ( BUF_SIZE ) ;
205+ let mut v: AVec < u8 , ConstAlign < ROOTLESS_MAX_PIPE_SIZE > > =
206+ AVec :: with_capacity ( ROOTLESS_MAX_PIPE_SIZE , BUF_SIZE ) ;
162207 let args = [ "foo" . into ( ) ] ;
163208 args_into_buffer ( & mut v, args. iter ( ) ) . unwrap ( ) ;
164- assert_eq ! ( String :: from_utf8( v) . unwrap( ) , "foo\n " ) ;
209+ assert_eq ! ( String :: from_utf8( v. to_vec ( ) ) . unwrap( ) , "foo\n " ) ;
165210 }
166211
167212 {
168- let mut v = Vec :: with_capacity ( BUF_SIZE ) ;
213+ let mut v: AVec < u8 , ConstAlign < ROOTLESS_MAX_PIPE_SIZE > > =
214+ AVec :: with_capacity ( ROOTLESS_MAX_PIPE_SIZE , BUF_SIZE ) ;
169215 let args = [ "foo" . into ( ) , "bar baz" . into ( ) , "qux" . into ( ) ] ;
170216 args_into_buffer ( & mut v, args. iter ( ) ) . unwrap ( ) ;
171- assert_eq ! ( String :: from_utf8( v) . unwrap( ) , "foo bar baz qux\n " ) ;
217+ assert_eq ! (
218+ String :: from_utf8( v. to_vec( ) ) . unwrap( ) ,
219+ "foo bar baz qux\n "
220+ ) ;
172221 }
173222 }
174223}
0 commit comments