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);