Skip to main content

coreshift_core/
fd.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/
4
5//! Owned file-descriptor primitives.
6//!
7//! [`Fd`] is the crate-wide owned descriptor type (move-only, closes on drop).
8//! [`Token`] and [`Event`] describe readiness events delivered by the
9//! [`crate::reactor::Reactor`].
10
11use crate::CoreError;
12use crate::error::syscall_ret;
13use std::io::Error as IoError;
14use std::time::Duration;
15
16#[inline(always)]
17fn errno() -> i32 {
18    IoError::last_os_error().raw_os_error().unwrap_or(0)
19}
20
21/// An owned file descriptor that closes on drop.
22///
23/// `Fd` is move-only. Constructing one from a raw descriptor transfers close
24/// ownership to `Fd`; do not also close the raw descriptor elsewhere.
25///
26/// ### Fork Safety
27/// `Fd` instances created by Core usually have `O_CLOEXEC` set. If the process
28/// forks, the descriptor will be inherited by the child but will be closed
29/// automatically upon `exec`. Callers that need a descriptor to survive `exec`
30/// must clear the flag manually.
31pub struct Fd(RawFd);
32
33use std::os::unix::io::{AsRawFd, RawFd};
34
35impl AsRawFd for Fd {
36    fn as_raw_fd(&self) -> RawFd {
37        self.0
38    }
39}
40
41impl Fd {
42    /// Wrap a raw file descriptor.
43    ///
44    /// # Errors
45    /// Returns a [`CoreError`] if the descriptor is negative.
46    #[inline(always)]
47    pub(crate) fn new(fd: RawFd, op: &'static str) -> Result<Self, CoreError> {
48        if fd < 0 {
49            Err(CoreError::sys(errno(), op))
50        } else {
51            Ok(Self(fd))
52        }
53    }
54
55    /// Wrap an owned raw file descriptor.
56    ///
57    /// # Safety
58    /// The caller must guarantee `fd` is valid, open, and uniquely owned by the
59    /// returned `Fd`. Passing a borrowed fd, or closing `fd` after this call,
60    /// can cause double-close or use-after-close bugs.
61    #[inline(always)]
62    pub unsafe fn from_owned_raw_fd(fd: RawFd, op: &'static str) -> Result<Self, CoreError> {
63        Self::new(fd, op)
64    }
65
66    /// Create a non-blocking `eventfd` with `EFD_CLOEXEC`.
67    ///
68    /// The descriptor is created with `FD_CLOEXEC` set.
69    ///
70    /// ### Errors
71    /// - `EINVAL`: `init` is invalid.
72    /// - `EMFILE`: Process limit on open file descriptors hit.
73    /// - `ENFILE`: System-wide limit on open files hit.
74    pub fn eventfd(init: u32) -> Result<Self, CoreError> {
75        let fd = unsafe { libc::eventfd(init, libc::EFD_CLOEXEC | libc::EFD_NONBLOCK) };
76        syscall_ret(fd, "eventfd")?;
77        Self::new(fd, "eventfd")
78    }
79
80    /// Create a non-blocking `timerfd` using `CLOCK_MONOTONIC` with `TFD_CLOEXEC`.
81    ///
82    /// The descriptor is created with `FD_CLOEXEC` set.
83    ///
84    /// ### Errors
85    /// - `EMFILE`: Process limit on open file descriptors hit.
86    /// - `ENFILE`: System-wide limit on open files hit.
87    /// - `ENOMEM`: Insufficient kernel memory.
88    pub fn timerfd() -> Result<Self, CoreError> {
89        let fd = unsafe {
90            libc::timerfd_create(
91                libc::CLOCK_MONOTONIC,
92                libc::TFD_CLOEXEC | libc::TFD_NONBLOCK,
93            )
94        };
95        syscall_ret(fd, "timerfd_create")?;
96        Self::new(fd, "timerfd_create")
97    }
98
99    /// Access the underlying raw file descriptor.
100    ///
101    /// NOTE: This is an escape hatch for low-level interactions. Prefer using
102    /// the safe methods on `Fd` or implementing `AsRawFd`.
103    #[inline(always)]
104    pub(crate) fn raw(&self) -> RawFd {
105        self.0
106    }
107
108    /// Duplicate this descriptor, returning a new owned `Fd`.
109    ///
110    /// Both descriptors share the same open file description, so a `dup` of
111    /// an eventfd remains a single signalable object: a write on either copy
112    /// is observed on the other. Useful for fan-out wakeups (hub keeps one
113    /// copy, a worker reactor owns another).
114    ///
115    /// ### Errors
116    /// - `EBADF`: The source descriptor is invalid.
117    /// - `EMFILE`: The process file descriptor limit is reached.
118    pub fn dup(&self) -> Result<Self, CoreError> {
119        let r = loop {
120            let d = unsafe { libc::dup(self.0) };
121            if d < 0 && errno() == libc::EINTR {
122                continue;
123            }
124            break d;
125        };
126        if r < 0 {
127            let e = errno();
128            Err(CoreError::sys(e, "dup"))
129        } else {
130            // SAFETY: `r` is a freshly duplicated owned descriptor.
131            unsafe { Self::from_owned_raw_fd(r, "dup") }
132        }
133    }
134
135    /// Perform a `dup2` syscall.
136    ///
137    /// ### Errors
138    /// - `EBADF`: The source or target file descriptor is invalid.
139    /// - `EMFILE`: The target descriptor exceeds the process limit.
140    pub fn dup2(&self, target: RawFd) -> Result<(), CoreError> {
141        loop {
142            let r = unsafe { libc::dup2(self.0, target) };
143            if r < 0 {
144                let e = errno();
145                if e == libc::EINTR {
146                    continue;
147                }
148                return syscall_ret(r, "dup2");
149            }
150            return Ok(());
151        }
152    }
153
154    /// Set the `O_NONBLOCK` flag on the descriptor.
155    ///
156    /// ### Errors
157    /// - `EBADF`: The file descriptor is invalid.
158    pub fn set_nonblock(&self) -> Result<(), CoreError> {
159        let flags = unsafe { libc::fcntl(self.0, libc::F_GETFL) };
160        syscall_ret(flags, "fcntl(F_GETFL)")?;
161        let r = unsafe { libc::fcntl(self.0, libc::F_SETFL, flags | libc::O_NONBLOCK) };
162        syscall_ret(r, "fcntl(F_SETFL)")
163    }
164
165    /// Set the `FD_CLOEXEC` flag on the descriptor.
166    ///
167    /// ### Errors
168    /// - `EBADF`: The file descriptor is invalid.
169    pub fn set_cloexec(&self) -> Result<(), CoreError> {
170        let flags = unsafe { libc::fcntl(self.0, libc::F_GETFD) };
171        syscall_ret(flags, "fcntl(F_GETFD)")?;
172        let r = unsafe { libc::fcntl(self.0, libc::F_SETFD, flags | libc::FD_CLOEXEC) };
173        syscall_ret(r, "fcntl(F_SETFD)")
174    }
175
176    /// Read bytes into a mutable slice.
177    ///
178    /// Returns `Ok(None)` if the operation would block (`EAGAIN`).
179    ///
180    /// ### Edge Cases
181    /// - **Zero-length read**: Returns `Ok(Some(0))` immediately.
182    /// - **Partial read**: Returns the number of bytes actually read.
183    ///
184    /// ### Errors
185    /// - `EBADF`: The file descriptor is invalid or not open for reading.
186    /// - `EFAULT`: `buf` points outside the process's address space.
187    /// - `EIO`: Low-level I/O error.
188    pub fn read_slice(&self, buf: &mut [u8]) -> Result<Option<usize>, CoreError> {
189        self.read_raw(buf.as_mut_ptr(), buf.len())
190    }
191
192    /// Seek to an absolute file offset.
193    ///
194    /// ### Errors
195    /// - `EBADF`: The file descriptor is not seekable.
196    /// - `EINVAL`: `offset` is invalid.
197    /// - `EOVERFLOW`: The resulting offset exceeds the off_t range.
198    pub fn seek_set(&self, offset: i64) -> Result<u64, CoreError> {
199        loop {
200            let pos = unsafe { libc::lseek(self.0, offset as libc::off_t, libc::SEEK_SET) };
201            if pos < 0 {
202                let e = errno();
203                if e == libc::EINTR {
204                    continue;
205                }
206                return Err(CoreError::sys(e, "lseek"));
207            }
208            return Ok(pos as u64);
209        }
210    }
211
212    /// Write bytes from a slice.
213    ///
214    /// Returns `Ok(None)` if the operation would block (`EAGAIN`).
215    ///
216    /// ### Edge Cases
217    /// - **Zero-length write**: Returns `Ok(Some(0))` immediately.
218    /// - **Partial write**: Returns the number of bytes actually written.
219    ///
220    /// ### Errors
221    /// - `EBADF`: The file descriptor is invalid or not open for writing.
222    /// - `EFAULT`: `buf` points outside the process's address space.
223    /// - `EPIPE`: The reading end of a pipe or socket was closed.
224    pub fn write_slice(&self, buf: &[u8]) -> Result<Option<usize>, CoreError> {
225        self.write_raw(buf.as_ptr(), buf.len())
226    }
227
228    /// Read a native-endian `u64`, blocking until data is available.
229    ///
230    /// Unlike `read_u64`, this never returns `Ok(None)` — it retries on `EINTR`
231    /// and returns `Err` only on a hard I/O failure. Intended for blocking
232    /// eventfds used as inter-thread notification primitives.
233    pub fn read_u64_blocking(&self) -> Result<u64, CoreError> {
234        let mut bytes = [0u8; std::mem::size_of::<u64>()];
235        loop {
236            let n =
237                unsafe { libc::read(self.0, bytes.as_mut_ptr() as *mut libc::c_void, bytes.len()) };
238            if n == bytes.len() as isize {
239                return Ok(u64::from_ne_bytes(bytes));
240            }
241            if n < 0 {
242                let e = errno();
243                if e == libc::EINTR {
244                    continue;
245                }
246                return Err(CoreError::sys(e, "read_u64_blocking"));
247            }
248            return Err(CoreError::sys(libc::EIO, "read_u64_blocking:short_read"));
249        }
250    }
251
252    /// Read a native-endian `u64`.
253    ///
254    /// Returns `Ok(None)` if the operation would block (`EAGAIN`).
255    pub fn read_u64(&self) -> Result<Option<u64>, CoreError> {
256        let mut bytes = [0u8; std::mem::size_of::<u64>()];
257        match self.read_slice(&mut bytes)? {
258            Some(n) if n == bytes.len() => Ok(Some(u64::from_ne_bytes(bytes))),
259            Some(_) => Err(CoreError::sys(libc::EIO, "read_u64")),
260            None => Ok(None),
261        }
262    }
263
264    /// Write a native-endian `u64`.
265    ///
266    /// Returns `Ok(None)` if the operation would block (`EAGAIN`).
267    pub fn write_u64(&self, value: u64) -> Result<Option<usize>, CoreError> {
268        self.write_slice(&value.to_ne_bytes())
269    }
270
271    /// Arm or disarm a one-shot `timerfd`.
272    ///
273    /// Passing `None` disarms the timer. Zero durations are rounded up to one
274    /// nanosecond so the timer still expires.
275    ///
276    /// ### Errors
277    /// - `EBADF`: The file descriptor is invalid.
278    /// - `EINVAL`: The duration is invalid or not supported by the kernel.
279    pub fn set_timer_oneshot(&self, delay: Option<Duration>) -> Result<(), CoreError> {
280        let mut spec: libc::itimerspec = unsafe { std::mem::zeroed() };
281        if let Some(delay) = delay {
282            let delay = delay.max(Duration::from_nanos(1));
283            spec.it_value.tv_sec = delay.as_secs() as libc::time_t;
284            spec.it_value.tv_nsec = delay.subsec_nanos() as libc::c_long;
285        }
286
287        let ret = unsafe { libc::timerfd_settime(self.raw(), 0, &spec, std::ptr::null_mut()) };
288        syscall_ret(ret, "timerfd_settime")
289    }
290
291    /// Read bytes into a raw buffer.
292    ///
293    /// Internal callers must ensure `buf` points to a valid writable region of
294    /// at least `count` bytes.
295    ///
296    /// Returns `Ok(None)` if the operation would block (`EAGAIN`).
297    pub(crate) fn read_raw(&self, buf: *mut u8, count: usize) -> Result<Option<usize>, CoreError> {
298        loop {
299            let n = unsafe { libc::read(self.0, buf as *mut libc::c_void, count) };
300            if n < 0 {
301                let e = errno();
302                if e == libc::EINTR {
303                    continue;
304                }
305                if e == libc::EAGAIN || e == libc::EWOULDBLOCK {
306                    return Ok(None);
307                }
308                return Err(CoreError::sys(e, "read"));
309            }
310            return Ok(Some(n as usize));
311        }
312    }
313
314    /// Write bytes from a raw buffer.
315    ///
316    /// Internal callers must ensure `buf` points to a valid readable region of
317    /// at least `count` bytes.
318    ///
319    /// Returns `Ok(None)` if the operation would block (`EAGAIN`).
320    pub(crate) fn write_raw(
321        &self,
322        buf: *const u8,
323        count: usize,
324    ) -> Result<Option<usize>, CoreError> {
325        loop {
326            let n = unsafe { libc::write(self.0, buf as *const libc::c_void, count) };
327            if n < 0 {
328                let e = errno();
329                if e == libc::EINTR {
330                    continue;
331                }
332                if e == libc::EAGAIN || e == libc::EWOULDBLOCK {
333                    return Ok(None);
334                }
335                return Err(CoreError::sys(e, "write"));
336            }
337            return Ok(Some(n as usize));
338        }
339    }
340}
341
342impl Drop for Fd {
343    fn drop(&mut self) {
344        if self.0 >= 0 {
345            unsafe {
346                libc::close(self.0);
347            }
348        }
349    }
350}
351
352/// An opaque token representing a registered file descriptor.
353#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
354pub struct Token(pub(crate) u64);
355
356#[allow(dead_code)]
357impl Token {
358    #[inline(always)]
359    pub(crate) fn new(val: u64) -> Self {
360        Self(val)
361    }
362
363    #[inline(always)]
364    pub(crate) fn val(&self) -> u64 {
365        self.0
366    }
367}
368
369/// A readiness event generated by the reactor.
370#[derive(Clone, Copy, Debug)]
371pub struct Event {
372    /// Token associated with the ready descriptor.
373    pub token: Token,
374    /// Descriptor is ready for reading (`EPOLLIN`).
375    pub readable: bool,
376    /// Descriptor has priority data or an exceptional condition (`EPOLLPRI`).
377    pub priority: bool,
378    /// Descriptor is ready for writing (`EPOLLOUT`).
379    pub writable: bool,
380    /// Indicates an error condition (`EPOLLERR`).
381    ///
382    /// NOTE: For edge-triggered readiness, an error condition often means both
383    /// readable and writable are set to ensure the handler drains the FD.
384    pub error: bool,
385    /// Indicates a remote hangup (`EPOLLHUP`).
386    pub hangup: bool,
387}
388
389const _: () = assert!(std::mem::size_of::<Event>() == 16);
390const _: () = assert!(std::mem::align_of::<Event>() == 8);