Skip to main content

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}