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,48 @@ 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+ #[ cfg( target_os = "linux" ) ]
36+ let buf_size = {
37+ use std:: os:: unix:: fs:: FileTypeExt ;
38+ // todo: detect pipe under masked /dev. This is really bad detection.
39+ if let Ok ( m) = std:: fs:: metadata ( "/dev/stdout" )
40+ && m. file_type ( ) . is_fifo ( )
41+ {
42+ let fd_raw = io:: stdout ( ) . as_raw_fd ( ) ;
43+ fcntl_setpipe_size (
44+ unsafe { BorrowedFd :: borrow_raw ( fd_raw) } ,
45+ ROOTLESS_MAX_PIPE_SIZE ,
46+ )
47+ . unwrap_or ( BUF_SIZE )
48+ } else {
49+ BUF_SIZE
50+ }
51+ } ;
52+ #[ cfg( not( target_os = "linux" ) ) ]
53+ let buf_size = BUF_SIZE ;
54+ let mut buffer: AVec < u8 , ConstAlign < ROOTLESS_MAX_PIPE_SIZE > > =
55+ AVec :: with_capacity ( ROOTLESS_MAX_PIPE_SIZE , buf_size) ;
2556 #[ allow( clippy:: unwrap_used, reason = "clap provides 'y' by default" ) ]
2657 let _ = args_into_buffer ( & mut buffer, matches. get_many :: < OsString > ( "STRING" ) . unwrap ( ) ) ;
27- prepare_buffer ( & mut buffer) ;
58+ prepare_buffer ( & mut buffer, buf_size ) ;
2859
2960 match exec ( & buffer) {
3061 Ok ( ( ) ) => Ok ( ( ) ) ,
@@ -55,8 +86,8 @@ pub fn uu_app() -> Command {
5586
5687/// Copies words from `i` into `buf`, separated by spaces.
5788#[ allow( clippy:: unnecessary_wraps, reason = "needed on some platforms" ) ]
58- fn args_into_buffer < ' a > (
59- buf : & mut Vec < u8 > ,
89+ fn args_into_buffer < ' a , A : Alignment > (
90+ buf : & mut AVec < u8 , A > ,
6091 i : impl Iterator < Item = & ' a OsString > ,
6192) -> Result < ( ) , Box < dyn Error > > {
6293 // On Unix (and wasi), OsStrs are just &[u8]'s underneath...
@@ -91,23 +122,25 @@ fn args_into_buffer<'a>(
91122
92123/// Assumes buf holds a single output line forged from the command line arguments, copies it
93124/// 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 {
125+ fn prepare_buffer < A : Alignment > ( buf : & mut AVec < u8 , A > , buf_size : usize ) {
126+ if buf. len ( ) * 2 > buf_size {
96127 return ;
97128 }
98129
99130 assert ! ( !buf. is_empty( ) ) ;
100131
101132 let line_len = buf. len ( ) ;
102- let target_size = line_len * ( BUF_SIZE / line_len) ;
133+ let target_size = line_len * ( buf_size / line_len) ;
103134
104135 while buf. len ( ) < target_size {
105- let to_copy = std:: cmp:: min ( target_size - buf. len ( ) , buf. len ( ) ) ;
136+ let current_len = buf. len ( ) ;
137+ let to_copy = std:: cmp:: min ( target_size - current_len, current_len) ;
106138 debug_assert_eq ! ( to_copy % line_len, 0 ) ;
107- buf. extend_from_within ( ..to_copy) ;
139+ buf. extend_from_slice ( & buf [ ..to_copy] . to_vec ( ) ) ;
108140 }
109141}
110142
143+ #[ cfg( not( target_os = "linux" ) ) ]
111144pub fn exec ( bytes : & [ u8 ] ) -> io:: Result < ( ) > {
112145 let stdout = io:: stdout ( ) ;
113146 let mut stdout = stdout. lock ( ) ;
@@ -117,6 +150,25 @@ pub fn exec(bytes: &[u8]) -> io::Result<()> {
117150 }
118151}
119152
153+ #[ cfg( target_os = "linux" ) ]
154+ pub fn exec ( bytes : & [ u8 ] ) -> io:: Result < ( ) > {
155+ let stdout = io:: stdout ( ) ;
156+ //zero copy fast-path
157+ //needed for large args
158+ //todo: align instead of giving up fast-path
159+ let aligned = bytes. len ( ) . is_multiple_of ( page_size ( ) ) ;
160+ if aligned {
161+ let fd_raw = stdout. as_raw_fd ( ) ;
162+ let fd = unsafe { BorrowedFd :: borrow_raw ( fd_raw) } ;
163+ let iovec = [ IoSliceRaw :: from_slice ( bytes) ] ;
164+ while unsafe { vmsplice ( fd, & iovec, SpliceFlags :: empty ( ) ) } . is_ok ( ) { }
165+ }
166+ let mut stdout = stdout. lock ( ) ;
167+ loop {
168+ stdout. write_all ( bytes) ?;
169+ }
170+ }
171+
120172#[ cfg( test) ]
121173mod tests {
122174 use super :: * ;
@@ -143,7 +195,7 @@ mod tests {
143195
144196 for ( line, final_len) in tests {
145197 let mut v = std:: iter:: repeat_n ( b'a' , line) . collect :: < Vec < _ > > ( ) ;
146- prepare_buffer ( & mut v) ;
198+ prepare_buffer ( & mut v, BUF_SIZE ) ;
147199 assert_eq ! ( v. len( ) , final_len) ;
148200 }
149201 }
0 commit comments