a10/fd.rs
1//! Asynchronous file descriptor (fd).
2//!
3//! See [`AsyncFd`].
4
5use std::os::fd::{BorrowedFd, IntoRawFd, OwnedFd, RawFd};
6use std::{fmt, io};
7
8use crate::SubmissionQueue;
9
10#[cfg(any(target_os = "android", target_os = "linux"))]
11pub use crate::sys::fd::{ToDirect, ToFd};
12
13/// An open file descriptor.
14///
15/// All functions on `AsyncFd` are asynchronous and return a [`Future`].
16///
17/// `AsyncFd` comes on in of two kinds:
18/// * regular file descriptor which can be used outside of A10.
19/// * direct descriptor which are only available io_uring and can only be used
20/// within A10.
21///
22/// Direct descriptors can be faster, but their usage is limited as they can
23/// only be used in io_uring operations and only by one, specific [`Ring`]. See
24/// [`AsyncFd::kind`] and [`Kind`] for more information.
25///
26/// An `AsyncFd` can be created using some of the following methods:
27/// * Sockets can be opened using [`socket`].
28/// * Sockets can also be accepted using [`AsyncFd::accept`].
29/// * Files can be opened using [`open_file`] or [`fs::OpenOptions`].
30/// * Finally they can be created from any valid file descriptor using
31/// [`AsyncFd::new`].
32///
33/// [`Ring`]: crate::Ring
34/// [`Future`]: std::future::Future
35/// [`socket`]: crate::net::socket
36/// [`open_file`]: crate::fs::open_file
37/// [`fs::OpenOptions`]: crate::fs::OpenOptions
38#[allow(clippy::module_name_repetitions)]
39pub struct AsyncFd {
40 fd: RawFd,
41 #[cfg(any(
42 target_os = "dragonfly",
43 target_os = "freebsd",
44 target_os = "ios",
45 target_os = "macos",
46 target_os = "netbsd",
47 target_os = "openbsd",
48 target_os = "tvos",
49 target_os = "visionos",
50 target_os = "watchos",
51 ))]
52 pub(crate) state: crate::sys::fd::State,
53 // NOTE: public because it's used by the crate::io::Std{in,out,error}.
54 pub(crate) sq: SubmissionQueue,
55}
56
57// NOTE: the implementations are split over the modules to give the `Future`
58// implementation types a reasonable place in the docs.
59
60impl AsyncFd {
61 /// Create a new `AsyncFd` from an owned file descriptor.
62 ///
63 /// # Notes
64 ///
65 /// `fd` is expected to be a regular file descriptor.
66 pub fn new(fd: OwnedFd, sq: SubmissionQueue) -> AsyncFd {
67 // SAFETY: OwnedFd ensure that `fd` is valid.
68 unsafe { AsyncFd::from_raw_fd(fd.into_raw_fd(), sq) }
69 }
70
71 /// Create a new `AsyncFd` from a raw file descriptor.
72 ///
73 /// # Notes
74 ///
75 /// `fd` is expected to be a regular file descriptor.
76 ///
77 /// # Safety
78 ///
79 /// The caller must ensure that `fd` is valid and that it's no longer used
80 /// by anything other than the returned `AsyncFd`.
81 pub unsafe fn from_raw_fd(fd: RawFd, sq: SubmissionQueue) -> AsyncFd {
82 // SAFETY: caller must ensure that `fd` is correct.
83 unsafe { AsyncFd::from_raw(fd, Kind::File, sq) }
84 }
85
86 pub(crate) unsafe fn from_raw(fd: RawFd, kind: Kind, sq: SubmissionQueue) -> AsyncFd {
87 #[cfg(any(target_os = "android", target_os = "linux"))]
88 let fd = if let Kind::Direct = kind {
89 fd | (1 << 31)
90 } else {
91 fd
92 };
93 #[cfg(any(
94 target_os = "dragonfly",
95 target_os = "freebsd",
96 target_os = "ios",
97 target_os = "macos",
98 target_os = "netbsd",
99 target_os = "openbsd",
100 target_os = "tvos",
101 target_os = "visionos",
102 target_os = "watchos",
103 ))]
104 let Kind::File = kind;
105 AsyncFd {
106 fd,
107 #[cfg(any(
108 target_os = "dragonfly",
109 target_os = "freebsd",
110 target_os = "ios",
111 target_os = "macos",
112 target_os = "netbsd",
113 target_os = "openbsd",
114 target_os = "tvos",
115 target_os = "visionos",
116 target_os = "watchos",
117 ))]
118 state: crate::sys::fd::State::new(),
119 sq,
120 }
121 }
122
123 /// Returns the kind of descriptor.
124 pub fn kind(&self) -> Kind {
125 #[cfg(any(target_os = "android", target_os = "linux"))]
126 if self.fd.is_negative() {
127 return Kind::Direct;
128 }
129 Kind::File
130 }
131
132 /// Attempts to borrow the file descriptor.
133 ///
134 /// If this is a direct descriptor this returns `None`. The direct
135 /// descriptor can be cloned into a regular file descriptor using
136 /// [`AsyncFd::to_file_descriptor`].
137 pub fn as_fd(&self) -> Option<BorrowedFd<'_>> {
138 #[cfg(any(target_os = "android", target_os = "linux"))]
139 if let Kind::Direct = self.kind() {
140 return None;
141 }
142
143 // SAFETY: we're ensured that `fd` is valid.
144 unsafe { Some(BorrowedFd::borrow_raw(self.fd())) }
145 }
146
147 /// Creates a new independently owned `AsyncFd` that shares the same
148 /// underlying file descriptor as the existing `AsyncFd`.
149 ///
150 /// # Notes
151 ///
152 /// Direct descriptors can not be cloned and will always return an
153 /// unsupported error.
154 #[doc(alias = "dup")]
155 #[doc(alias = "dup2")]
156 #[doc(alias = "F_DUPFD")]
157 #[doc(alias = "F_DUPFD_CLOEXEC")]
158 pub fn try_clone(&self) -> io::Result<AsyncFd> {
159 let fd = self.as_fd().ok_or(io::ErrorKind::Unsupported)?;
160 let fd = fd.try_clone_to_owned()?;
161 Ok(AsyncFd::new(fd, self.sq.clone()))
162 }
163
164 /// Returns the `RawFd` of this `AsyncFd`.
165 ///
166 /// The file descriptor can be a regular or direct descriptor.
167 pub(crate) fn fd(&self) -> RawFd {
168 // The sign bit is used to indicate direct descriptors, so unset it.
169 self.fd & !(1 << 31)
170 }
171
172 /// Returns the internal state of the fd.
173 #[cfg(any(
174 target_os = "dragonfly",
175 target_os = "freebsd",
176 target_os = "ios",
177 target_os = "macos",
178 target_os = "netbsd",
179 target_os = "openbsd",
180 target_os = "tvos",
181 target_os = "visionos",
182 target_os = "watchos",
183 ))]
184 pub(crate) const fn state(&self) -> &crate::sys::fd::State {
185 &self.state
186 }
187
188 /// Returns the `SubmissionQueue` of this `AsyncFd`.
189 pub(crate) const fn sq(&self) -> &SubmissionQueue {
190 &self.sq
191 }
192}
193
194impl Unpin for AsyncFd {}
195
196#[allow(clippy::missing_fields_in_debug)] // Don't care about sq.
197impl fmt::Debug for AsyncFd {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 let mut f = f.debug_struct("AsyncFd");
200 f.field("fd", &self.fd()).field("kind", &self.kind());
201 #[cfg(any(
202 target_os = "dragonfly",
203 target_os = "freebsd",
204 target_os = "ios",
205 target_os = "macos",
206 target_os = "netbsd",
207 target_os = "openbsd",
208 target_os = "tvos",
209 target_os = "visionos",
210 target_os = "watchos",
211 ))]
212 f.field("state", &self.state);
213 f.finish()
214 }
215}
216
217/// Kind of descriptor.
218#[derive(Copy, Clone, Debug, Eq, PartialEq)]
219#[non_exhaustive]
220pub enum Kind {
221 /// Regular Unix file descriptor.
222 File,
223 /// Direct descriptor are io_uring private file descriptor.
224 ///
225 /// They avoid some of the overhead associated with thread shared file
226 /// tables and can be used in any io_uring request that takes a file
227 /// descriptor. However they cannot be used outside of io_uring.
228 #[cfg(any(target_os = "android", target_os = "linux"))]
229 Direct,
230}
231
232impl Kind {
233 #[allow(clippy::semicolon_if_nothing_returned, clippy::unused_self)]
234 pub(crate) fn cloexec_flag(self) -> libc::c_int {
235 #[cfg(any(target_os = "android", target_os = "linux"))]
236 if let Kind::Direct = self {
237 return 0; // Direct descriptor always have (the equivalant of) `O_CLOEXEC` set.
238 }
239 // We also use `O_CLOEXEC` when we technically should use
240 // `SOCK_CLOEXEC`, so ensure the value is the same so it works as
241 // expected.
242 #[cfg(any(target_os = "android", target_os = "linux"))]
243 #[allow(clippy::items_after_statements)]
244 const _: () = assert!(libc::SOCK_CLOEXEC == libc::O_CLOEXEC);
245 libc::O_CLOEXEC
246 }
247}