system_interface/io/
is_read_write.rs

1use std::io;
2#[cfg(not(windows))]
3use {io_lifetimes::AsFilelike, rustix::io::is_read_write};
4#[cfg(windows)]
5use {
6    std::{
7        os::windows::io::{AsRawSocket, RawSocket},
8        ptr,
9    },
10    windows_sys::Win32::Networking::WinSock::{
11        recv, send, MSG_PEEK, SOCKET, SOCKET_ERROR, WSAEFAULT, WSAESHUTDOWN, WSAEWOULDBLOCK,
12    },
13};
14
15/// A trait for the `is_read_write` function.
16pub trait IsReadWrite {
17    /// Test whether the handle is readable and/or writable.
18    fn is_read_write(&self) -> io::Result<(bool, bool)>;
19}
20
21#[cfg(not(windows))]
22impl<T: AsFilelike> IsReadWrite for T {
23    #[inline]
24    fn is_read_write(&self) -> io::Result<(bool, bool)> {
25        Ok(is_read_write(self)?)
26    }
27}
28
29#[cfg(windows)]
30impl IsReadWrite for std::fs::File {
31    #[inline]
32    fn is_read_write(&self) -> io::Result<(bool, bool)> {
33        file_is_read_write(self)
34    }
35}
36
37#[cfg(all(windows, feature = "cap_std_impls"))]
38impl IsReadWrite for cap_std::fs::File {
39    #[inline]
40    fn is_read_write(&self) -> io::Result<(bool, bool)> {
41        use io_lifetimes::AsFilelike;
42        file_is_read_write(&self.as_filelike_view::<std::fs::File>())
43    }
44}
45
46#[cfg(all(windows, feature = "cap_std_impls_fs_utf8"))]
47impl IsReadWrite for cap_std::fs_utf8::File {
48    #[inline]
49    fn is_read_write(&self) -> io::Result<(bool, bool)> {
50        use io_lifetimes::AsFilelike;
51        file_is_read_write(&self.as_filelike_view::<std::fs::File>())
52    }
53}
54
55#[cfg(all(windows, feature = "async-std"))]
56impl IsReadWrite for async_std::fs::File {
57    #[inline]
58    fn is_read_write(&self) -> io::Result<(bool, bool)> {
59        use io_lifetimes::AsFilelike;
60        file_is_read_write(&self.as_filelike_view::<std::fs::File>())
61    }
62}
63
64#[cfg(all(windows, feature = "async-std"))]
65impl IsReadWrite for cap_async_std::fs::File {
66    #[inline]
67    fn is_read_write(&self) -> io::Result<(bool, bool)> {
68        use io_lifetimes::AsFilelike;
69        file_is_read_write(&self.as_filelike_view::<std::fs::File>())
70    }
71}
72
73#[cfg(all(
74    windows,
75    feature = "async-std",
76    feature = "cap_async_std_impls_fs_utf8"
77))]
78impl IsReadWrite for cap_async_std::fs_utf8::File {
79    #[inline]
80    fn is_read_write(&self) -> io::Result<(bool, bool)> {
81        use io_lifetimes::AsFilelike;
82        file_is_read_write(&self.as_filelike_view::<std::fs::File>())
83    }
84}
85
86#[cfg(windows)]
87impl IsReadWrite for std::net::TcpStream {
88    #[inline]
89    fn is_read_write(&self) -> io::Result<(bool, bool)> {
90        raw_socket_is_read_write(self.as_raw_socket())
91    }
92}
93
94#[cfg(all(windows, feature = "cap_std_impls"))]
95impl IsReadWrite for cap_std::net::TcpStream {
96    #[inline]
97    fn is_read_write(&self) -> io::Result<(bool, bool)> {
98        raw_socket_is_read_write(self.as_raw_socket())
99    }
100}
101
102#[cfg(all(windows, feature = "cap_async_std_impls"))]
103impl IsReadWrite for async_std::net::TcpStream {
104    #[inline]
105    fn is_read_write(&self) -> io::Result<(bool, bool)> {
106        raw_socket_is_read_write(self.as_raw_socket())
107    }
108}
109
110#[cfg(all(windows, feature = "cap_async_std_impls"))]
111impl IsReadWrite for cap_async_std::net::TcpStream {
112    #[inline]
113    fn is_read_write(&self) -> io::Result<(bool, bool)> {
114        raw_socket_is_read_write(self.as_raw_socket())
115    }
116}
117
118#[cfg(windows)]
119#[inline]
120fn file_is_read_write(file: &std::fs::File) -> std::io::Result<(bool, bool)> {
121    cap_fs_ext::IsFileReadWrite::is_file_read_write(file)
122}
123
124#[cfg(windows)]
125fn raw_socket_is_read_write(raw_socket: RawSocket) -> io::Result<(bool, bool)> {
126    let (mut read, mut write) = (true, true);
127
128    // Detect write shutdown. A zero-length `send` doesn't block but does
129    // provide a helpful error message.
130    let socket = raw_socket as SOCKET;
131    let write_result = unsafe { send(socket, ptr::null_mut(), 0, 0) };
132    if write_result == SOCKET_ERROR {
133        let err = io::Error::last_os_error();
134        match err.raw_os_error() {
135            Some(WSAESHUTDOWN) => write = false,
136            Some(WSAEWOULDBLOCK) => (),
137            _ => return Err(err),
138        }
139    }
140
141    // Detect read shutdown. A normal zero-length `recv` does block, so
142    // use deliberately invalid pointer, as we get different error codes in
143    // the case of a shut-down stream.
144    let read_result = unsafe { recv(socket, usize::MAX as *mut _, 1, MSG_PEEK) };
145    if read_result == SOCKET_ERROR {
146        let err = io::Error::last_os_error();
147        match err.raw_os_error() {
148            Some(WSAEFAULT) => (),
149            Some(WSAESHUTDOWN) => read = false,
150            _ => return Err(err),
151        }
152    }
153
154    Ok((read, write))
155}
156
157#[cfg(all(windows, feature = "socket2"))]
158impl IsReadWrite for socket2::Socket {
159    #[inline]
160    fn is_read_write(&self) -> io::Result<(bool, bool)> {
161        use io_lifetimes::AsSocketlike;
162        self.as_socketlike_view::<std::net::TcpStream>()
163            .is_read_write()
164    }
165}