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}