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
88use clap:: { Arg , ArgAction , Command , builder:: ValueParser } ;
99use std:: error:: Error ;
@@ -13,18 +13,46 @@ use uucore::error::{UResult, USimpleError};
1313use uucore:: format_usage;
1414use uucore:: translate;
1515
16+ #[ cfg( target_os = "linux" ) ]
17+ use rustix:: {
18+ fd:: { AsRawFd , BorrowedFd } ,
19+ param:: page_size,
20+ pipe:: { IoSliceRaw , SpliceFlags , fcntl_setpipe_size, vmsplice} ,
21+ } ;
1622// it's possible that using a smaller or larger buffer might provide better performance on some
1723// systems, but honestly this is good enough
1824const BUF_SIZE : usize = 16 * 1024 ;
1925
2026#[ uucore:: main]
2127pub fn uumain ( args : impl uucore:: Args ) -> UResult < ( ) > {
2228 let matches = uucore:: clap_localization:: handle_clap_result ( uu_app ( ) , args) ?;
29+ // use larger pipe if zero-copy is possible
30+ // todo: deduplicate logic
31+ #[ cfg( target_os = "linux" ) ]
32+ let buf_size = {
33+ use std:: os:: unix:: fs:: FileTypeExt ;
34+ // todo: detect pipe under masked /dev. This is really bad detection.
35+ if let Ok ( m) = std:: fs:: metadata ( "/dev/stdout" )
36+ && m. file_type ( ) . is_fifo ( )
37+ {
38+ const ROOTLESS_MAX_PIPE_SIZE : usize = 1024 * 1024 ;
39+ let fd_raw = io:: stdout ( ) . as_raw_fd ( ) ;
40+ fcntl_setpipe_size (
41+ unsafe { BorrowedFd :: borrow_raw ( fd_raw) } ,
42+ ROOTLESS_MAX_PIPE_SIZE ,
43+ )
44+ . unwrap_or ( BUF_SIZE )
45+ } else {
46+ BUF_SIZE
47+ }
48+ } ;
49+ #[ cfg( not( target_os = "linux" ) ) ]
50+ let buf_size = BUF_SIZE ;
2351
24- let mut buffer = Vec :: with_capacity ( BUF_SIZE ) ;
52+ let mut buffer = Vec :: with_capacity ( buf_size ) ;
2553 #[ allow( clippy:: unwrap_used, reason = "clap provides 'y' by default" ) ]
2654 let _ = args_into_buffer ( & mut buffer, matches. get_many :: < OsString > ( "STRING" ) . unwrap ( ) ) ;
27- prepare_buffer ( & mut buffer) ;
55+ prepare_buffer ( & mut buffer, buf_size ) ;
2856
2957 match exec ( & buffer) {
3058 Ok ( ( ) ) => Ok ( ( ) ) ,
@@ -91,15 +119,15 @@ fn args_into_buffer<'a>(
91119
92120/// Assumes buf holds a single output line forged from the command line arguments, copies it
93121/// 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 {
122+ fn prepare_buffer ( buf : & mut Vec < u8 > , buf_size : usize ) {
123+ if buf. len ( ) * 2 > buf_size {
96124 return ;
97125 }
98126
99127 assert ! ( !buf. is_empty( ) ) ;
100128
101129 let line_len = buf. len ( ) ;
102- let target_size = line_len * ( BUF_SIZE / line_len) ;
130+ let target_size = line_len * ( buf_size / line_len) ;
103131
104132 while buf. len ( ) < target_size {
105133 let to_copy = std:: cmp:: min ( target_size - buf. len ( ) , buf. len ( ) ) ;
@@ -108,6 +136,7 @@ fn prepare_buffer(buf: &mut Vec<u8>) {
108136 }
109137}
110138
139+ #[ cfg( not( target_os = "linux" ) ) ]
111140pub fn exec ( bytes : & [ u8 ] ) -> io:: Result < ( ) > {
112141 let stdout = io:: stdout ( ) ;
113142 let mut stdout = stdout. lock ( ) ;
@@ -117,6 +146,24 @@ pub fn exec(bytes: &[u8]) -> io::Result<()> {
117146 }
118147}
119148
149+ #[ cfg( target_os = "linux" ) ]
150+ pub fn exec ( bytes : & [ u8 ] ) -> io:: Result < ( ) > {
151+ let stdout = io:: stdout ( ) ;
152+ //zero copy fast-path
153+ //todo: we should align instead of giving up fast-path
154+ let aligned = bytes. len ( ) . is_multiple_of ( page_size ( ) ) ;
155+ if aligned {
156+ let fd_raw = stdout. as_raw_fd ( ) ;
157+ let fd = unsafe { BorrowedFd :: borrow_raw ( fd_raw) } ;
158+ let iovec = [ IoSliceRaw :: from_slice ( bytes) ] ;
159+ while unsafe { vmsplice ( fd, & iovec, SpliceFlags :: empty ( ) ) } . is_ok ( ) { }
160+ }
161+ let mut stdout = stdout. lock ( ) ;
162+ loop {
163+ stdout. write_all ( bytes) ?;
164+ }
165+ }
166+
120167#[ cfg( test) ]
121168mod tests {
122169 use super :: * ;
@@ -143,7 +190,7 @@ mod tests {
143190
144191 for ( line, final_len) in tests {
145192 let mut v = std:: iter:: repeat_n ( b'a' , line) . collect :: < Vec < _ > > ( ) ;
146- prepare_buffer ( & mut v) ;
193+ prepare_buffer ( & mut v, BUF_SIZE ) ;
147194 assert_eq ! ( v. len( ) , final_len) ;
148195 }
149196 }
0 commit comments