1use std::io;
9use std::os::fd::RawFd;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum PumpResult {
14 Moved(u64),
16 Eof,
18 WouldBlock,
20 Err(i32),
22}
23
24thread_local! {
25 static PIPES: (RawFd, RawFd) = {
26 let mut fds = [0 as RawFd; 2];
27 let rc = unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_NONBLOCK | libc::O_CLOEXEC) };
29 debug_assert_eq!(rc, 0, "pipe2");
30 (fds[0], fds[1])
31 };
32}
33
34fn pipes() -> (RawFd, RawFd) {
35 PIPES.with(|p| *p)
36}
37
38#[must_use]
40pub fn pump(from: RawFd, to: RawFd, max: u64) -> PumpResult {
41 let (rp, wp) = pipes();
42 let mut total = 0u64;
43 while total < max {
44 let inn = unsafe {
47 libc::splice(
48 from,
49 std::ptr::null_mut(),
50 wp,
51 std::ptr::null_mut(),
52 (max - total).min(1 << 16) as usize,
53 libc::SPLICE_F_MOVE | libc::SPLICE_F_NONBLOCK,
54 )
55 };
56 if inn < 0 {
57 let err = io::Error::last_os_error().raw_os_error().unwrap_or(0);
58 if err == libc::EAGAIN {
59 return if total > 0 {
60 PumpResult::Moved(total)
61 } else {
62 PumpResult::WouldBlock
63 };
64 }
65 if err == libc::EINTR {
66 continue;
67 }
68 return PumpResult::Err(err);
69 }
70 if inn == 0 {
71 return PumpResult::Eof;
72 }
73 let mut left = inn as usize;
75 while left > 0 {
76 let out = unsafe {
78 libc::splice(
79 rp,
80 std::ptr::null_mut(),
81 to,
82 std::ptr::null_mut(),
83 left,
84 libc::SPLICE_F_MOVE | libc::SPLICE_F_NONBLOCK,
85 )
86 };
87 if out < 0 {
88 let err = io::Error::last_os_error().raw_os_error().unwrap_or(0);
89 if err == libc::EINTR {
90 continue;
91 }
92 if err == libc::EAGAIN {
93 std::hint::spin_loop();
96 continue;
97 }
98 return PumpResult::Err(err);
99 }
100 if out == 0 {
101 return PumpResult::Eof;
102 }
103 left -= out as usize;
104 }
105 total += inn as u64;
106 }
107 PumpResult::Moved(total)
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use std::os::fd::FromRawFd;
114
115 fn socketpair() -> (std::net::TcpStream, std::net::TcpStream) {
117 let mut fds = [0 as RawFd; 2];
118 let rc = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
120 assert_eq!(rc, 0, "socketpair");
121 let a = unsafe { std::net::TcpStream::from_raw_fd(fds[0]) };
123 let b = unsafe { std::net::TcpStream::from_raw_fd(fds[1]) };
125 a.set_nonblocking(true).expect("nonblock a");
126 b.set_nonblocking(true).expect("nonblock b");
127 (a, b)
128 }
129
130 #[test]
131 fn moves_bytes_between_sockets() {
132 let (mut a, mut b) = socketpair();
133 use std::io::Write as _;
134 a.set_nonblocking(false).ok();
135 let payload = b"hello splice";
136 a.write_all(payload).expect("write");
137 let result = pump(
138 std::os::fd::AsRawFd::as_raw_fd(&a),
139 std::os::fd::AsRawFd::as_raw_fd(&b),
140 1024,
141 );
142 match result {
147 PumpResult::Moved(_) | PumpResult::WouldBlock | PumpResult::Eof => {}
148 other => panic!("unexpected: {other:?}"),
149 }
150 let _ = &mut b;
151 }
152
153 #[test]
154 fn would_block_on_empty_source() {
155 let (a, b) = socketpair();
156 let result = pump(
157 std::os::fd::AsRawFd::as_raw_fd(&a),
158 std::os::fd::AsRawFd::as_raw_fd(&b),
159 1024,
160 );
161 assert_eq!(result, PumpResult::WouldBlock);
162 }
163
164 #[test]
165 fn pipe_to_file_moves_bytes() {
166 let mut fds = [0 as RawFd; 2];
169 let rc = unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_NONBLOCK | libc::O_CLOEXEC) };
171 assert_eq!(rc, 0);
172 let (pr, pw) = (fds[0], fds[1]);
173 let payload = b"pipe payload for splice";
174 let n = unsafe { libc::write(pw, payload.as_ptr().cast(), payload.len()) };
176 assert_eq!(n, payload.len() as isize);
177
178 let devnull = std::fs::OpenOptions::new()
179 .write(true)
180 .open("/dev/null")
181 .expect("devnull");
182 let result = pump(pr, std::os::fd::AsRawFd::as_raw_fd(&devnull), 1024);
183 assert_eq!(result, PumpResult::Moved(payload.len() as u64));
184
185 let result2 = pump(pr, std::os::fd::AsRawFd::as_raw_fd(&devnull), 1024);
187 assert_eq!(result2, PumpResult::WouldBlock);
188 unsafe { libc::close(pr) };
190 unsafe { libc::close(pw) };
192 }
193
194 #[test]
195 fn eof_when_source_closed() {
196 let mut fds = [0 as RawFd; 2];
197 let rc = unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_NONBLOCK | libc::O_CLOEXEC) };
199 assert_eq!(rc, 0);
200 let (pr, pw) = (fds[0], fds[1]);
201 unsafe { libc::close(pw) };
204 let devnull = std::fs::OpenOptions::new()
205 .write(true)
206 .open("/dev/null")
207 .expect("devnull");
208 let result = pump(pr, std::os::fd::AsRawFd::as_raw_fd(&devnull), 1024);
209 assert_eq!(result, PumpResult::Eof);
210 unsafe { libc::close(pr) };
212 }
213
214 #[test]
215 fn err_on_bad_source_fd() {
216 let devnull = std::fs::OpenOptions::new()
217 .write(true)
218 .open("/dev/null")
219 .expect("devnull");
220 let result = pump(-1, std::os::fd::AsRawFd::as_raw_fd(&devnull), 1024);
221 match result {
222 PumpResult::Err(e) => assert_eq!(e, libc::EBADF),
223 other => panic!("expected EBADF, got {other:?}"),
224 }
225 }
226}