1use std::{
10 io::{Read, Stdin, Write},
11 os::fd::AsFd,
12};
13
14use nix::{errno::Errno, sys::sendfile::sendfile64};
15
16use crate::{
17 compat::{fstatx, STATX_SIZE},
18 cookie::{safe_read, safe_write},
19 err2no,
20 fd::SafeOwnedFd,
21 retry::retry_on_eintr,
22};
23
24pub fn read_buf<Fd: AsFd>(fd: Fd, buf: &mut [u8]) -> Result<usize, Errno> {
28 let mut nread = 0;
29
30 while nread < buf.len() {
31 match retry_on_eintr(|| safe_read(&fd, &mut buf[nread..]))? {
32 0 => break,
33 n => nread = nread.checked_add(n).ok_or(Errno::EOVERFLOW)?,
34 }
35 }
36
37 Ok(nread)
38}
39
40pub fn read_all<Fd: AsFd>(fd: Fd) -> Result<Vec<u8>, Errno> {
44 let mut buf = Vec::new();
45
46 let size = fstatx(&fd, STATX_SIZE)
47 .map(|stx| stx.stx_size)
48 .and_then(|size| usize::try_from(size).or(Err(Errno::EOVERFLOW)))?;
49 if size == 0 {
50 return Ok(buf);
51 }
52
53 buf.try_reserve(size).or(Err(Errno::ENOMEM))?;
54 buf.resize(size, 0);
55
56 let n = read_buf(fd, &mut buf)?;
57 buf.truncate(n);
58
59 Ok(buf)
60}
61
62pub fn write_all<Fd: AsFd>(fd: Fd, data: &[u8]) -> Result<(), Errno> {
66 let mut nwrite = 0;
67
68 while nwrite < data.len() {
69 match retry_on_eintr(|| safe_write(&fd, &data[nwrite..]))? {
70 0 => return Err(Errno::EPIPE),
71 n => nwrite = nwrite.checked_add(n).ok_or(Errno::EOVERFLOW)?,
72 }
73 }
74
75 Ok(())
76}
77
78pub trait ReadFd: AsFd + Read {}
80
81pub trait WriteFd: AsFd + Write {}
83
84#[expect(clippy::disallowed_types)]
85impl ReadFd for std::fs::File {}
86impl ReadFd for Stdin {}
87impl ReadFd for SafeOwnedFd {}
88
89#[expect(clippy::disallowed_types)]
90impl WriteFd for std::fs::File {}
91impl WriteFd for SafeOwnedFd {}
92
93pub fn copy<Fd1, Fd2>(src: &mut Fd1, dst: &mut Fd2) -> Result<u64, Errno>
98where
99 Fd1: ReadFd,
100 Fd2: WriteFd,
101{
102 const MAX: usize = 0x7ffff000;
106
107 let mut ncopy = 0;
108 loop {
109 return match sendfile64(&dst, &src, None, MAX) {
110 Ok(0) => Ok(ncopy),
111 Ok(n) => {
112 let n = n.try_into().or(Err(Errno::EOVERFLOW))?;
113 ncopy = ncopy.checked_add(n).ok_or(Errno::EOVERFLOW)?;
114 continue;
115 }
116 Err(Errno::EINTR) => continue,
117 Err(Errno::EINVAL | Errno::ENOSYS) =>
118 {
119 #[expect(clippy::disallowed_methods)]
120 std::io::copy(src, dst).map_err(|err| err2no(&err))
121 }
122 Err(errno) => Err(errno),
123 };
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use std::io::{Seek, SeekFrom, Write as IoWrite};
130
131 use super::*;
132
133 fn tempfile_with(data: &[u8]) -> std::fs::File {
134 let mut f = tempfile::tempfile().unwrap();
135 f.write_all(data).unwrap();
136 f.seek(SeekFrom::Start(0)).unwrap();
137 f
138 }
139
140 #[test]
141 fn test_read_buf_1() {
142 let f = tempfile_with(b"hello");
143 let mut buf = [0u8; 5];
144 let n = read_buf(&f, &mut buf).unwrap();
145 assert_eq!(n, 5);
146 assert_eq!(&buf, b"hello");
147 }
148
149 #[test]
150 fn test_read_buf_2() {
151 let f = tempfile_with(b"hi");
152 let mut buf = [0u8; 10];
153 let n = read_buf(&f, &mut buf).unwrap();
154 assert_eq!(n, 2);
155 assert_eq!(&buf[..n], b"hi");
156 }
157
158 #[test]
159 fn test_read_buf_3() {
160 let f = tempfile_with(b"");
161 let mut buf = [0u8; 4];
162 let n = read_buf(&f, &mut buf).unwrap();
163 assert_eq!(n, 0);
164 }
165
166 #[test]
167 fn test_read_buf_4() {
168 let f = tempfile_with(b"abc");
169 let mut buf = [];
170 let n = read_buf(&f, &mut buf).unwrap();
171 assert_eq!(n, 0);
172 }
173
174 #[test]
175 fn test_read_all_1() {
176 let f = tempfile_with(b"syd rocks");
177 let data = read_all(&f).unwrap();
178 assert_eq!(data, b"syd rocks");
179 }
180
181 #[test]
182 fn test_read_all_2() {
183 let f = tempfile_with(b"");
184 let data = read_all(&f).unwrap();
185 assert!(data.is_empty());
186 }
187
188 #[test]
189 fn test_read_all_3() {
190 let payload = vec![0xffu8; 8192];
191 let f = tempfile_with(&payload);
192 let data = read_all(&f).unwrap();
193 assert_eq!(data, payload);
194 }
195
196 #[test]
197 fn test_write_all_1() {
198 let f = tempfile::tempfile().unwrap();
199 write_all(&f, b"hello world").unwrap();
200
201 let mut f = f;
202 f.seek(SeekFrom::Start(0)).unwrap();
203 let mut out = Vec::new();
204 std::io::Read::read_to_end(&mut f, &mut out).unwrap();
205 assert_eq!(out, b"hello world");
206 }
207
208 #[test]
209 fn test_write_all_2() {
210 let f = tempfile::tempfile().unwrap();
211 write_all(&f, b"").unwrap();
212
213 let mut f = f;
214 f.seek(SeekFrom::Start(0)).unwrap();
215 let mut out = Vec::new();
216 std::io::Read::read_to_end(&mut f, &mut out).unwrap();
217 assert!(out.is_empty());
218 }
219
220 #[test]
221 fn test_write_all_3() {
222 let payload = vec![0xabu8; 16384];
223 let f = tempfile::tempfile().unwrap();
224 write_all(&f, &payload).unwrap();
225
226 let mut f = f;
227 f.seek(SeekFrom::Start(0)).unwrap();
228 let mut out = Vec::new();
229 std::io::Read::read_to_end(&mut f, &mut out).unwrap();
230 assert_eq!(out, payload);
231 }
232
233 #[test]
234 fn test_copy_1() {
235 let mut src = tempfile_with(b"copy me");
236 let mut dst = tempfile::tempfile().unwrap();
237 let n = copy(&mut src, &mut dst).unwrap();
238 assert_eq!(n, 7);
239
240 dst.seek(SeekFrom::Start(0)).unwrap();
241 let mut out = Vec::new();
242 std::io::Read::read_to_end(&mut dst, &mut out).unwrap();
243 assert_eq!(out, b"copy me");
244 }
245
246 #[test]
247 fn test_copy_2() {
248 let mut src = tempfile_with(b"");
249 let mut dst = tempfile::tempfile().unwrap();
250 let n = copy(&mut src, &mut dst).unwrap();
251 assert_eq!(n, 0);
252 }
253
254 #[test]
255 fn test_copy_3() {
256 let payload = vec![0x42u8; 65536];
257 let mut src = tempfile_with(&payload);
258 let mut dst = tempfile::tempfile().unwrap();
259 let n = copy(&mut src, &mut dst).unwrap();
260 assert_eq!(n as usize, payload.len());
261
262 dst.seek(SeekFrom::Start(0)).unwrap();
263 let mut out = Vec::new();
264 std::io::Read::read_to_end(&mut dst, &mut out).unwrap();
265 assert_eq!(out, payload);
266 }
267
268 #[test]
269 fn test_readfd_1() {
270 let mut f = tempfile_with(b"trait test");
271 fn accept_readfd(r: &mut dyn ReadFd) -> Vec<u8> {
272 let mut buf = Vec::new();
273 r.read_to_end(&mut buf).unwrap();
274 buf
275 }
276 let data = accept_readfd(&mut f);
277 assert_eq!(data, b"trait test");
278 }
279
280 #[test]
281 fn test_writefd_1() {
282 let mut f = tempfile::tempfile().unwrap();
283 fn accept_writefd(w: &mut dyn WriteFd, data: &[u8]) {
284 w.write_all(data).unwrap();
285 }
286 accept_writefd(&mut f, b"trait write");
287
288 f.seek(SeekFrom::Start(0)).unwrap();
289 let mut out = Vec::new();
290 std::io::Read::read_to_end(&mut f, &mut out).unwrap();
291 assert_eq!(out, b"trait write");
292 }
293}