77
88#[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
99use rustix:: pipe:: { SpliceFlags , fcntl_setpipe_size} ;
10- #[ cfg( any( target_os = "linux" , target_os = "android" , test) ) ]
11- use std:: fs:: File ;
1210#[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
1311use std:: os:: fd:: AsFd ;
12+ #[ cfg( any( target_os = "linux" , target_os = "android" , test) ) ]
13+ use std:: { fs:: File , io:: Read } ;
1414#[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
1515pub const MAX_ROOTLESS_PIPE_SIZE : usize = 1024 * 1024 ;
16+ #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
17+ const KERNEL_DEFAULT_PIPE_SIZE : usize = 64 * 1024 ;
1618
1719/// A wrapper around [`rustix::pipe::pipe`] that ensures the pipe is cleaned up.
1820///
@@ -30,6 +32,20 @@ pub fn pipe() -> std::io::Result<(File, File)> {
3032 Ok ( ( File :: from ( read) , File :: from ( write) ) )
3133}
3234
35+ /// return pipe with given size or kernel's default size
36+ ///
37+ /// useful to save RAM usage
38+ #[ inline]
39+ #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
40+ fn pipe_with_size ( s : usize ) -> std:: io:: Result < ( File , File ) > {
41+ let ( read, write) = rustix:: pipe:: pipe ( ) ?;
42+ if s > KERNEL_DEFAULT_PIPE_SIZE {
43+ let _ = fcntl_setpipe_size ( & read, s) ;
44+ }
45+
46+ Ok ( ( File :: from ( read) , File :: from ( write) ) )
47+ }
48+
3349/// Less noisy wrapper around [`rustix::pipe::splice`].
3450///
3551/// Up to `len` bytes are moved from `source` to `target`. Returns the number
@@ -72,6 +88,87 @@ pub fn might_fuse(source: &impl AsFd) -> bool {
7288 . unwrap_or ( true )
7389}
7490
91+ /// splice `n` bytes with safe read/write fallback
92+ /// return actually sent bytes
93+ #[ inline]
94+ #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
95+ pub fn send_n_bytes (
96+ input : impl Read + AsFd ,
97+ mut target : impl std:: io:: Write + AsFd ,
98+ n : u64 ,
99+ ) -> std:: io:: Result < u64 > {
100+ let pipe_size = MAX_ROOTLESS_PIPE_SIZE . min ( n as usize ) ;
101+ let mut n = n;
102+ let mut bytes_written: u64 = 0 ;
103+ // do not always fallback to write as it needs 2 Ctrl+D on tty
104+ let mut needs_fallback = might_fuse ( & input) ;
105+ if let Ok ( b) = splice ( & input, & target, n as usize ) {
106+ bytes_written = b as u64 ;
107+ n -= bytes_written;
108+ if n > 0 {
109+ // for throughput. expected that input is already extended if it is coming from splice
110+ if pipe_size > KERNEL_DEFAULT_PIPE_SIZE {
111+ let _ = fcntl_setpipe_size ( & target, pipe_size) ;
112+ }
113+ } else {
114+ // avoid unnecessary syscalls
115+ return Ok ( bytes_written) ;
116+ }
117+
118+ loop {
119+ match splice ( & input, & target, n as usize ) {
120+ Ok ( 0 ) => break ,
121+ Ok ( s @ 1 ..) => {
122+ n -= s as u64 ;
123+ bytes_written += s as u64 ;
124+ }
125+ _ => {
126+ needs_fallback = true ;
127+ break ;
128+ }
129+ }
130+ }
131+ } else if let Ok ( ( broker_r, broker_w) ) = pipe_with_size ( pipe_size) {
132+ // todo: allow reusing broker pipe. needed for using splice to uutils/split in the future
133+ loop {
134+ match splice ( & input, & broker_w, n as usize ) {
135+ Ok ( 0 ) => break ,
136+ Ok ( s @ 1 ..) => {
137+ if splice_exact ( & broker_r, & target, s) . is_ok ( ) {
138+ n -= s as u64 ;
139+ bytes_written += s as u64 ;
140+ } else {
141+ let mut drain = Vec :: with_capacity ( s) ; // bounded by pipe size
142+ broker_r. take ( s as u64 ) . read_to_end ( & mut drain) ?;
143+ target. write_all ( & drain) ?;
144+ needs_fallback = true ;
145+ break ;
146+ }
147+ }
148+ _ => {
149+ needs_fallback = true ;
150+ break ;
151+ }
152+ }
153+ }
154+ }
155+
156+ if !needs_fallback {
157+ return Ok ( bytes_written) ;
158+ }
159+ let mut reader = input. take ( n) ;
160+ let mut buf = vec ! [ 0u8 ; ( 32 * 1024 ) . min( n as usize ) ] ; //use heap to avoid early allocation
161+ loop {
162+ match reader. read ( & mut buf) ? {
163+ 0 => return Ok ( bytes_written) ,
164+ n => {
165+ target. write_all ( & buf[ ..n] ) ?;
166+ bytes_written += n as u64 ;
167+ }
168+ }
169+ }
170+ }
171+
75172/// Return verified /dev/null
76173///
77174/// `splice` to /dev/null is faster than `read` when we skip or count the non-seekable input
0 commit comments