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,17 +13,45 @@ 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 args_into_buffer ( & mut buffer, matches. get_many :: < OsString > ( "STRING" ) ) . unwrap ( ) ;
26- prepare_buffer ( & mut buffer) ;
54+ prepare_buffer ( & mut buffer, buf_size ) ;
2755
2856 match exec ( & buffer) {
2957 Ok ( ( ) ) => Ok ( ( ) ) ,
@@ -94,15 +122,15 @@ fn args_into_buffer<'a>(
94122
95123/// Assumes buf holds a single output line forged from the command line arguments, copies it
96124/// repeatedly until the buffer holds as many copies as it can under [`BUF_SIZE`].
97- fn prepare_buffer ( buf : & mut Vec < u8 > ) {
98- if buf. len ( ) * 2 > BUF_SIZE {
125+ fn prepare_buffer ( buf : & mut Vec < u8 > , buf_size : usize ) {
126+ if buf. len ( ) * 2 > buf_size {
99127 return ;
100128 }
101129
102130 assert ! ( !buf. is_empty( ) ) ;
103131
104132 let line_len = buf. len ( ) ;
105- let target_size = line_len * ( BUF_SIZE / line_len) ;
133+ let target_size = line_len * ( buf_size / line_len) ;
106134
107135 while buf. len ( ) < target_size {
108136 let to_copy = std:: cmp:: min ( target_size - buf. len ( ) , buf. len ( ) ) ;
@@ -111,6 +139,7 @@ fn prepare_buffer(buf: &mut Vec<u8>) {
111139 }
112140}
113141
142+ #[ cfg( not( target_os = "linux" ) ) ]
114143pub fn exec ( bytes : & [ u8 ] ) -> io:: Result < ( ) > {
115144 let stdout = io:: stdout ( ) ;
116145 let mut stdout = stdout. lock ( ) ;
@@ -120,6 +149,24 @@ pub fn exec(bytes: &[u8]) -> io::Result<()> {
120149 }
121150}
122151
152+ #[ cfg( target_os = "linux" ) ]
153+ pub fn exec ( bytes : & [ u8 ] ) -> io:: Result < ( ) > {
154+ let stdout = io:: stdout ( ) ;
155+ //zero copy fast-path
156+ //todo: we should align instead of giving up fast-path
157+ let aligned = bytes. len ( ) . is_multiple_of ( page_size ( ) ) ;
158+ if aligned {
159+ let fd_raw = stdout. as_raw_fd ( ) ;
160+ let fd = unsafe { BorrowedFd :: borrow_raw ( fd_raw) } ;
161+ let iovec = [ IoSliceRaw :: from_slice ( bytes) ] ;
162+ while unsafe { vmsplice ( fd, & iovec, SpliceFlags :: empty ( ) ) } . is_ok ( ) { }
163+ }
164+ let mut stdout = stdout. lock ( ) ;
165+ loop {
166+ stdout. write_all ( bytes) ?;
167+ }
168+ }
169+
123170#[ cfg( test) ]
124171mod tests {
125172 use super :: * ;
@@ -146,7 +193,7 @@ mod tests {
146193
147194 for ( line, final_len) in tests {
148195 let mut v = std:: iter:: repeat_n ( b'a' , line) . collect :: < Vec < _ > > ( ) ;
149- prepare_buffer ( & mut v) ;
196+ prepare_buffer ( & mut v, BUF_SIZE ) ;
150197 assert_eq ! ( v. len( ) , final_len) ;
151198 }
152199 }
0 commit comments