system_interface/io/
read_ready.rs

1use crate::io::IsReadWrite;
2use io_lifetimes::raw::{AsRawFilelike, FromRawFilelike};
3#[cfg(not(any(windows, target_os = "redox")))]
4use rustix::io::ioctl_fionread;
5use std::io::{self, Seek, SeekFrom, Stdin, StdinLock};
6#[cfg(not(target_os = "redox"))]
7use std::net;
8use std::process::{ChildStderr, ChildStdout};
9#[cfg(windows)]
10use {
11    std::{mem::MaybeUninit, os::windows::io::AsRawSocket},
12    windows_sys::Win32::Networking::WinSock::{ioctlsocket, FIONREAD, SOCKET},
13};
14
15/// Extension for readable streams that can indicate the number of bytes
16/// ready to be read immediately.
17pub trait ReadReady {
18    /// Return the number of bytes which are ready to be read immediately.
19    ///
20    /// The returned number may be greater than the number of bytes actually
21    /// readable if the end of the stream is known to be reachable without
22    /// blocking.
23    fn num_ready_bytes(&self) -> io::Result<u64>;
24}
25
26/// Implement `ReadReady` for `Stdin`.
27#[cfg(not(any(windows, target_os = "redox")))]
28impl ReadReady for Stdin {
29    #[inline]
30    fn num_ready_bytes(&self) -> io::Result<u64> {
31        Ok(ioctl_fionread(self)?)
32    }
33}
34
35/// Implement `ReadReady` for `Stdin`.
36#[cfg(any(windows, target_os = "redox"))]
37impl ReadReady for Stdin {
38    #[inline]
39    fn num_ready_bytes(&self) -> io::Result<u64> {
40        // Return the conservatively correct result.
41        Ok(0)
42    }
43}
44
45/// Implement `ReadReady` for `StdinLock`.
46#[cfg(not(any(windows, target_os = "redox")))]
47impl<'a> ReadReady for StdinLock<'a> {
48    #[inline]
49    fn num_ready_bytes(&self) -> io::Result<u64> {
50        Ok(ioctl_fionread(self)?)
51    }
52}
53
54/// Implement `ReadReady` for `StdinLock`.
55#[cfg(any(windows, target_os = "redox"))]
56impl<'a> ReadReady for StdinLock<'a> {
57    #[inline]
58    fn num_ready_bytes(&self) -> io::Result<u64> {
59        // Return the conservatively correct result.
60        Ok(0)
61    }
62}
63
64impl crate::io::ReadReady for std::fs::File {
65    #[inline]
66    fn num_ready_bytes(&self) -> std::io::Result<u64> {
67        let (read, _write) = self.is_read_write()?;
68        if read {
69            // If it's a file, we can query how many bytes are left.
70            let metadata = self.metadata()?;
71            if metadata.is_file() {
72                // When `File::tell` is stable use that, but for now, create a
73                // temporary `File` so that we can get a `&mut File` and call
74                // `seek` on it.
75                let mut tmp = unsafe { std::fs::File::from_raw_filelike(self.as_raw_filelike()) };
76                let current = tmp.seek(SeekFrom::Current(0));
77                std::mem::forget(tmp);
78                return Ok(metadata.len() - current?);
79            }
80
81            // Otherwise, try our luck with `FIONREAD`.
82            #[cfg(unix)]
83            if let Ok(n) = ioctl_fionread(self) {
84                return Ok(n);
85            }
86
87            // It's something without a specific length that we can't query,
88            // so return the conservatively correct answer.
89            return Ok(0);
90        }
91        Err(std::io::Error::new(
92            std::io::ErrorKind::InvalidInput,
93            "stream is not readable",
94        ))
95    }
96}
97
98/// Implement `ReadReady` for `std::net::TcpStream`.
99#[cfg(not(any(windows, target_os = "redox")))]
100impl ReadReady for net::TcpStream {
101    #[inline]
102    fn num_ready_bytes(&self) -> io::Result<u64> {
103        Ok(ioctl_fionread(self)?)
104    }
105}
106
107/// Implement `ReadReady` for `std::net::TcpStream`.
108#[cfg(not(any(windows, target_os = "redox")))]
109#[cfg(feature = "cap_std_impls")]
110impl ReadReady for cap_std::net::TcpStream {
111    #[inline]
112    fn num_ready_bytes(&self) -> io::Result<u64> {
113        use io_lifetimes::AsSocketlike;
114        self.as_socketlike_view::<net::TcpStream>()
115            .num_ready_bytes()
116    }
117}
118
119/// Implement `ReadReady` for `std::net::TcpStream`.
120#[cfg(target_os = "redox")]
121impl ReadReady for net::TcpStream {
122    #[inline]
123    fn num_ready_bytes(&self) -> io::Result<u64> {
124        // Return the conservatively correct result.
125        Ok(0)
126    }
127}
128
129/// Implement `ReadReady` for `std::net::TcpStream`.
130#[cfg(target_os = "redox")]
131impl ReadReady for cap_std::net::TcpStream {
132    #[inline]
133    fn num_ready_bytes(&self) -> io::Result<u64> {
134        use io_lifetimes::AsSocketlike;
135        self.as_socketlike_view::<net::TcpStream>()
136            .num_ready_bytes()
137    }
138}
139
140/// Implement `ReadReady` for `std::os::unix::net::UnixStream`.
141#[cfg(unix)]
142impl ReadReady for std::os::unix::net::UnixStream {
143    #[inline]
144    fn num_ready_bytes(&self) -> io::Result<u64> {
145        Ok(ioctl_fionread(self)?)
146    }
147}
148
149/// Implement `ReadReady` for `&[u8]`.
150impl ReadReady for &[u8] {
151    #[inline]
152    fn num_ready_bytes(&self) -> io::Result<u64> {
153        Ok(self.len() as u64)
154    }
155}
156
157/// Implement `ReadReady` for `std::io::Empty`.
158impl ReadReady for std::io::Empty {
159    #[inline]
160    fn num_ready_bytes(&self) -> io::Result<u64> {
161        // Empty has zero bytes, but say 1 so that consumers know that they
162        // can immediately attempt to read 1 byte and it won't block.
163        Ok(1)
164    }
165}
166
167/// Implement `ReadReady` for `std::io::Repeat`.
168impl ReadReady for std::io::Repeat {
169    #[inline]
170    fn num_ready_bytes(&self) -> io::Result<u64> {
171        // Unlimited bytes available immediately.
172        Ok(u64::MAX)
173    }
174}
175
176/// Implement `ReadReady` for `std::collections::VecDeque<T>`.
177impl<T> ReadReady for std::collections::VecDeque<T> {
178    #[inline]
179    fn num_ready_bytes(&self) -> io::Result<u64> {
180        Ok(self.len() as u64)
181    }
182}
183
184/// Implement `ReadReady` for `Box`.
185impl<R: ReadReady> ReadReady for Box<R> {
186    #[inline]
187    fn num_ready_bytes(&self) -> io::Result<u64> {
188        self.as_ref().num_ready_bytes()
189    }
190}
191
192/// Implement `ReadReady` for `std::io::BufReader<R>`.
193impl<R: std::io::Read + ReadReady> ReadReady for std::io::BufReader<R> {
194    #[inline]
195    fn num_ready_bytes(&self) -> io::Result<u64> {
196        let buffer = self.buffer();
197        match self.get_ref().num_ready_bytes() {
198            Ok(n) => Ok(buffer.len() as u64 + n),
199            Err(_) if !buffer.is_empty() => Ok(buffer.len() as u64),
200            Err(e) => Err(e),
201        }
202    }
203}
204
205/// Implement `ReadReady` for `std::io::Cursor<T>`.
206impl<T: AsRef<[u8]>> ReadReady for std::io::Cursor<T> {
207    #[inline]
208    fn num_ready_bytes(&self) -> io::Result<u64> {
209        Ok(self.get_ref().as_ref().len() as u64 - self.position())
210    }
211}
212
213/// Implement `ReadReady` for `std::io::Take<T>`.
214impl<T: ReadReady> ReadReady for std::io::Take<T> {
215    #[inline]
216    fn num_ready_bytes(&self) -> io::Result<u64> {
217        Ok(std::cmp::min(
218            self.limit(),
219            self.get_ref().num_ready_bytes()?,
220        ))
221    }
222}
223
224/// Implement `ReadReady` for `std::io::Chain<T, U>`.
225impl<T: ReadReady, U> ReadReady for std::io::Chain<T, U> {
226    #[inline]
227    fn num_ready_bytes(&self) -> io::Result<u64> {
228        // Just return the ready bytes for the first source, since we don't
229        // know if it'll block before we exhaust it and move to the second.
230        self.get_ref().0.num_ready_bytes()
231    }
232}
233
234/// Implement `ReadReady` for `std::net::TcpStream`.
235#[cfg(windows)]
236impl ReadReady for net::TcpStream {
237    #[inline]
238    fn num_ready_bytes(&self) -> io::Result<u64> {
239        let mut arg = MaybeUninit::<std::os::raw::c_ulong>::uninit();
240        if unsafe { ioctlsocket(self.as_raw_socket() as SOCKET, FIONREAD, arg.as_mut_ptr()) } == 0 {
241            Ok(unsafe { arg.assume_init() }.into())
242        } else {
243            Err(io::Error::last_os_error())
244        }
245    }
246}
247
248/// Implement `ReadReady` for `std::net::TcpStream`.
249#[cfg(windows)]
250#[cfg(feature = "cap_std_impls")]
251impl ReadReady for cap_std::net::TcpStream {
252    #[inline]
253    fn num_ready_bytes(&self) -> io::Result<u64> {
254        use io_lifetimes::AsSocketlike;
255        self.as_socketlike_view::<net::TcpStream>()
256            .num_ready_bytes()
257    }
258}
259
260/// Implement `ReadReady` for `std::net::TcpStream`.
261#[cfg(feature = "socket2")]
262impl ReadReady for socket2::Socket {
263    #[inline]
264    fn num_ready_bytes(&self) -> io::Result<u64> {
265        use io_lifetimes::AsSocketlike;
266        self.as_socketlike_view::<std::net::TcpStream>()
267            .num_ready_bytes()
268    }
269}
270
271#[cfg(all(not(windows), feature = "os_pipe"))]
272impl ReadReady for os_pipe::PipeReader {
273    #[inline]
274    fn num_ready_bytes(&self) -> io::Result<u64> {
275        Ok(ioctl_fionread(self)?)
276    }
277}
278
279#[cfg(all(windows, feature = "os_pipe"))]
280impl ReadReady for os_pipe::PipeReader {
281    #[inline]
282    fn num_ready_bytes(&self) -> io::Result<u64> {
283        // Return the conservatively correct result.
284        Ok(0)
285    }
286}
287
288#[cfg(not(windows))]
289impl ReadReady for ChildStdout {
290    #[inline]
291    fn num_ready_bytes(&self) -> io::Result<u64> {
292        Ok(ioctl_fionread(self)?)
293    }
294}
295
296#[cfg(windows)]
297impl ReadReady for ChildStdout {
298    #[inline]
299    fn num_ready_bytes(&self) -> io::Result<u64> {
300        // Return the conservatively correct result.
301        Ok(0)
302    }
303}
304
305#[cfg(not(windows))]
306impl ReadReady for ChildStderr {
307    #[inline]
308    fn num_ready_bytes(&self) -> io::Result<u64> {
309        Ok(ioctl_fionread(self)?)
310    }
311}
312
313#[cfg(windows)]
314impl ReadReady for ChildStderr {
315    #[inline]
316    fn num_ready_bytes(&self) -> io::Result<u64> {
317        // Return the conservatively correct result.
318        Ok(0)
319    }
320}
321
322#[cfg(feature = "socketpair")]
323impl ReadReady for socketpair::SocketpairStream {
324    #[inline]
325    fn num_ready_bytes(&self) -> io::Result<u64> {
326        socketpair::SocketpairStream::num_ready_bytes(self)
327    }
328}
329
330#[cfg(feature = "ssh2")]
331impl ReadReady for ssh2::Channel {
332    #[inline]
333    fn num_ready_bytes(&self) -> io::Result<u64> {
334        Ok(u64::from(self.read_window().available))
335    }
336}
337
338#[cfg(feature = "char-device")]
339impl ReadReady for char_device::CharDevice {
340    #[inline]
341    fn num_ready_bytes(&self) -> io::Result<u64> {
342        char_device::CharDevice::num_ready_bytes(self)
343    }
344}
345
346/// Implement `ReadReady` for `cap_std::fs::File`.
347#[cfg(feature = "cap_std_impls")]
348impl ReadReady for cap_std::fs::File {
349    #[inline]
350    fn num_ready_bytes(&self) -> io::Result<u64> {
351        use io_lifetimes::AsFilelike;
352        self.as_filelike_view::<std::fs::File>().num_ready_bytes()
353    }
354}
355
356/// Implement `ReadReady` for `cap_std::fs_utf8::File`.
357#[cfg(feature = "cap_std_impls_fs_utf8")]
358impl ReadReady for cap_std::fs_utf8::File {
359    #[inline]
360    fn num_ready_bytes(&self) -> io::Result<u64> {
361        use io_lifetimes::AsFilelike;
362        self.as_filelike_view::<std::fs::File>().num_ready_bytes()
363    }
364}