a10/
fd.rs

1//! Asynchronous file descriptor (fd).
2//!
3//! See [`AsyncFd`].
4
5use std::marker::PhantomData;
6use std::mem::ManuallyDrop;
7use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
8use std::{fmt, io};
9
10use crate::op::{op_future, Submission};
11use crate::SubmissionQueue;
12use crate::libc::{self, syscall};
13
14/// An open file descriptor.
15///
16/// All functions on `AsyncFd` are asynchronous and return a [`Future`].
17///
18/// `AsyncFd` comes on in of two kinds:
19///  * `AsyncFd<`[`File`]`>`: regular file descriptor which can be used as a
20///    regular file descriptor outside of io_uring.
21///  * `AsyncFd<`[`Direct`]`>`: direct descriptor which can be only be used with
22///    io_uring.
23///
24/// Direct descriptors can be faster, but their usage is limited to them being
25/// limited to io_uring operations.
26///
27/// An `AsyncFd` can be created using some of the following methods:
28///  * Sockets can be opened using [`socket`].
29///  * Sockets can also be accepted using [`AsyncFd::accept`].
30///  * Files can be opened using [`open_file`] or [`fs::OpenOptions`].
31///  * Finally they can be created from any valid file descriptor using
32///    [`AsyncFd::new`].
33///
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<D: Descriptor = File> {
40    /// # Notes
41    ///
42    /// We use `ManuallyDrop` because we drop the fd using io_uring, not a
43    /// blocking `close(2)` system call.
44    fd: ManuallyDrop<OwnedFd>,
45    pub(crate) sq: SubmissionQueue,
46    kind: PhantomData<D>,
47}
48
49// NOTE: the implementations are split over the modules to give the `Future`
50// implementation types a reasonable place in the docs.
51
52/// Operations only available on regular file descriptors.
53impl AsyncFd<File> {
54    /// Create a new `AsyncFd`.
55    pub const fn new(fd: OwnedFd, sq: SubmissionQueue) -> AsyncFd {
56        AsyncFd {
57            fd: ManuallyDrop::new(fd),
58            sq,
59            kind: PhantomData,
60        }
61    }
62
63    /// Create a new `AsyncFd` from a `RawFd`.
64    ///
65    /// # Safety
66    ///
67    /// The caller must ensure that `fd` is valid and that it's no longer used
68    /// by anything other than the returned `AsyncFd`.
69    pub unsafe fn from_raw_fd(fd: RawFd, sq: SubmissionQueue) -> AsyncFd {
70        AsyncFd::new(OwnedFd::from_raw_fd(fd), sq)
71    }
72
73    /// Creates a new independently owned `AsyncFd` that shares the same
74    /// underlying file descriptor as the existing `AsyncFd`.
75    #[doc(alias = "dup")]
76    #[doc(alias = "dup2")]
77    #[doc(alias = "F_DUPFD")]
78    #[doc(alias = "F_DUPFD_CLOEXEC")]
79    pub fn try_clone(&self) -> io::Result<AsyncFd> {
80        let fd = self.fd.try_clone()?;
81        Ok(AsyncFd::new(fd, self.sq.clone()))
82    }
83
84    /// Convert a regular file descriptor into a direct descriptor.
85    ///
86    /// The file descriptor can continued to be used and the lifetimes of the
87    /// file descriptor and the newly returned direct descriptor are not
88    /// connected.
89    ///
90    /// # Notes
91    ///
92    /// The [`Ring`] must be configured [`with_direct_descriptors`] enabled,
93    /// otherwise this will return `ENXIO`.
94    ///
95    /// [`Ring`]: crate::Ring
96    /// [`with_direct_descriptors`]: crate::Config::with_direct_descriptors
97    #[doc(alias = "IORING_OP_FILES_UPDATE")]
98    #[doc(alias = "IORING_FILE_INDEX_ALLOC")]
99    pub fn to_direct_descriptor<'fd>(&'fd self) -> ToDirect<'fd, File> {
100        ToDirect::new(self, Box::new(self.fd()), ())
101    }
102}
103
104/// Operations only available on direct descriptors.
105impl AsyncFd<Direct> {
106    /// Create a new `AsyncFd` from a direct descriptor.
107    ///
108    /// # Safety
109    ///
110    /// The caller must ensure that `direct_fd` is valid and that it's no longer
111    /// used by anything other than the returned `AsyncFd`. Furthermore the
112    /// caller must ensure the direct descriptor is actually a direct
113    /// descriptor.
114    pub(crate) unsafe fn from_direct_fd(direct_fd: RawFd, sq: SubmissionQueue) -> AsyncFd<Direct> {
115        AsyncFd::from_raw(direct_fd, sq)
116    }
117
118    /// Convert a direct descriptor into a regular file descriptor.
119    ///
120    /// The direct descriptor can continued to be used and the lifetimes of the
121    /// direct descriptor and the newly returned file descriptor are not
122    /// connected.
123    ///
124    /// # Notes
125    ///
126    /// Requires Linux 6.8.
127    #[doc(alias = "IORING_OP_FIXED_FD_INSTALL")]
128    pub const fn to_file_descriptor<'fd>(&'fd self) -> ToFd<'fd, Direct> {
129        ToFd::new(self, ())
130    }
131}
132
133impl<D: Descriptor> AsyncFd<D> {
134    /// Create a new `AsyncFd` from a direct descriptor.
135    ///
136    /// # Safety
137    ///
138    /// The caller must ensure that `fd` is valid and that it's no longer
139    /// used by anything other than the returned `AsyncFd`. Furthermore the
140    /// caller must ensure the descriptor is file or direct descriptor depending
141    /// on `D`.
142    pub(crate) unsafe fn from_raw(fd: RawFd, sq: SubmissionQueue) -> AsyncFd<D> {
143        AsyncFd {
144            fd: ManuallyDrop::new(OwnedFd::from_raw_fd(fd)),
145            sq,
146            kind: PhantomData,
147        }
148    }
149
150    /// Returns the `RawFd` of this `AsyncFd`.
151    ///
152    /// The file descriptor can be a regular or direct descriptor.
153    pub(crate) fn fd(&self) -> RawFd {
154        self.fd.as_raw_fd()
155    }
156}
157
158impl AsFd for AsyncFd<File> {
159    fn as_fd(&self) -> BorrowedFd<'_> {
160        self.fd.as_fd()
161    }
162}
163
164impl<D: Descriptor> fmt::Debug for AsyncFd<D> {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        struct AsyncFdSubmissionQueue<'a>(&'a SubmissionQueue);
167
168        impl fmt::Debug for AsyncFdSubmissionQueue<'_> {
169            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170                f.debug_struct("SubmissionQueue")
171                    .field("ring_fd", &self.0.shared.ring_fd.as_raw_fd())
172                    .finish()
173            }
174        }
175
176        f.debug_struct("AsyncFd")
177            .field("fd", &self.fd())
178            .field("sq", &AsyncFdSubmissionQueue(&self.sq))
179            .field("kind", &D::fmt_dbg())
180            .finish()
181    }
182}
183
184impl<D: Descriptor> Drop for AsyncFd<D> {
185    fn drop(&mut self) {
186        let result = self.sq.add_no_result(|submission| unsafe {
187            submission.close(self.fd());
188            submission.no_completion_event();
189            D::use_flags(submission);
190        });
191        if let Err(err) = result {
192            log::warn!("error submitting close operation for a10::AsyncFd: {err}");
193            if let Err(err) = D::close(self.fd()) {
194                log::warn!("error closing a10::AsyncFd: {err}");
195            }
196        }
197    }
198}
199
200/// What kind of descriptor is used [`File`] or [`Direct`].
201#[allow(private_bounds)] // That's the point of the private module.
202pub trait Descriptor: private::Descriptor {}
203
204pub(crate) mod private {
205    use std::os::fd::RawFd;
206    use std::io;
207
208    use crate::op::Submission;
209
210    pub(crate) trait Descriptor {
211        /// Set any additional flags in `submission` when using the descriptor.
212        fn use_flags(submission: &mut Submission);
213
214        /// Set any additional flags in `submission` when creating the descriptor.
215        fn create_flags(submission: &mut Submission);
216
217        /// Return the equivalant of `O_CLOEXEC` for the descripor.
218        fn cloexec_flag() -> libc::c_int;
219
220        /// Return the equivalant of `IORING_ASYNC_CANCEL_FD_FIXED` for the
221        /// descriptor.
222        fn cancel_flag() -> u32;
223
224        /// Debug representation of the descriptor.
225        fn fmt_dbg() -> &'static str;
226
227        fn close(fd: RawFd) -> io::Result<()>;
228    }
229}
230
231/// Regular Unix file descriptors.
232#[derive(Copy, Clone, Debug)]
233pub enum File {}
234
235impl Descriptor for File {}
236
237impl private::Descriptor for File {
238    fn use_flags(_: &mut Submission) {
239        // No additional flags needed.
240    }
241
242    fn create_flags(_: &mut Submission) {
243        // No additional flags needed.
244    }
245
246    fn cloexec_flag() -> libc::c_int {
247        libc::O_CLOEXEC
248    }
249
250    fn cancel_flag() -> u32 {
251        0
252    }
253
254    fn fmt_dbg() -> &'static str {
255        "file descriptor"
256    }
257
258    fn close(fd: RawFd) -> io::Result<()> {
259        syscall!(close(fd))?;
260        Ok(())
261    }
262}
263
264/// Direct descriptors are io_uring private file descriptors.
265///
266/// They avoid some of the overhead associated with thread shared file tables
267/// and can be used in any io_uring request that takes a file descriptor.
268/// However they cannot be used outside of io_uring.
269#[derive(Copy, Clone, Debug)]
270pub enum Direct {}
271
272impl Descriptor for Direct {}
273
274impl private::Descriptor for Direct {
275    fn use_flags(submission: &mut Submission) {
276        submission.use_direct_fd();
277    }
278
279    fn create_flags(submission: &mut Submission) {
280        submission.create_direct_fd();
281    }
282
283    fn cloexec_flag() -> libc::c_int {
284        0 // Direct descriptor always have (the equivalant of) `O_CLOEXEC` set.
285    }
286
287    fn cancel_flag() -> u32 {
288        libc::IORING_ASYNC_CANCEL_FD_FIXED
289    }
290
291    fn fmt_dbg() -> &'static str {
292        "direct descriptor"
293    }
294
295    fn close(fd: RawFd) -> io::Result<()> {
296        // TODO: don't leak the the fd.
297        log::warn!("leaking direct descriptor {fd}");
298        Ok(())
299    }
300}
301
302// ToFd.
303op_future! {
304    fn AsyncFd::to_file_descriptor -> AsyncFd<File>,
305    struct ToFd<'fd> {
306        // No state needed.
307    },
308    setup_state: _unused: (),
309    setup: |submission, fd, (), ()| unsafe {
310        // NOTE: flags must currently be null.
311        submission.create_file_descriptor(fd.fd(), 0);
312    },
313    map_result: |this, (), fd| {
314        let sq = this.fd.sq.clone();
315        // SAFETY: the fixed fd intall operation ensures that `fd` is valid.
316        let fd = unsafe { AsyncFd::from_raw_fd(fd, sq) };
317        Ok(fd)
318    },
319}
320
321// ToDirect.
322// NOTE: keep this in sync with the `process::ToSignalsDirect` implementation.
323op_future! {
324    fn AsyncFd::to_direct_descriptor -> AsyncFd<Direct>,
325    struct ToDirect<'fd> {
326        /// The file descriptor we're changing into a direct descriptor, needs
327        /// to stay in memory so the kernel can access it safely.
328        direct_fd: Box<RawFd>,
329    },
330    setup_state: _unused: (),
331    setup: |submission, fd, (direct_fd,), ()| unsafe {
332        submission.create_direct_descriptor(&mut **direct_fd, 1);
333    },
334    map_result: |this, (direct_fd,), res| {
335        debug_assert!(res == 1);
336        let sq = this.fd.sq.clone();
337        // SAFETY: the files update operation ensures that `direct_fd` is valid.
338        let fd = unsafe { AsyncFd::from_direct_fd(*direct_fd, sq) };
339        Ok(fd)
340    },
341}