iceoryx2_bb_linux/
signalfd.rs

1// Copyright (c) 2025 Contributors to the Eclipse Foundation
2//
3// See the NOTICE file(s) distributed with this work for additional
4// information regarding copyright ownership.
5//
6// This program and the accompanying materials are made available under the
7// terms of the Apache Software License 2.0 which is available at
8// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
9// which is available at https://opensource.org/licenses/MIT.
10//
11// SPDX-License-Identifier: Apache-2.0 OR MIT
12
13//! The [`SignalFd`] is a safe abstraction over the linux signal fd api. It
14//! allows users to receive a [`FetchableSignal`] via a [`FileDescriptor`] that can be attached
15//! to a
16//! [`FileDescriptorSet`](iceoryx2_bb_posix::file_descriptor_set::FileDescriptorSet)
17//! or via [`Epoll`](crate::epoll::Epoll).
18//!
19//! # Example
20//!
21//! ```
22//! # extern crate iceoryx2_loggers;
23//!
24//! use iceoryx2_bb_linux::signalfd::SignalFdBuilder;
25//! use iceoryx2_bb_posix::signal_set::FetchableSignalSet;
26//! use iceoryx2_bb_posix::signal::FetchableSignal;
27//!
28//! # fn main() -> Result<(), Box<dyn core::error::Error>> {
29//!
30//! let mut registered_signals = FetchableSignalSet::new_empty();
31//! registered_signals.add(FetchableSignal::UserDefined1);
32//!
33//! let signal_fd = SignalFdBuilder::new(registered_signals).create_non_blocking()?;
34//!
35//! match signal_fd.try_read()? {
36//!     Some(signal) => println!("signal was raised: {signal:?}"),
37//!     None => println!("no signal was raised")
38//! }
39//!
40//! # Ok(())
41//! # }
42//! ```
43
44use core::fmt::Debug;
45
46use iceoryx2_bb_posix::{
47    file_descriptor::{FileDescriptor, FileDescriptorBased},
48    file_descriptor_set::SynchronousMultiplexing,
49    process::ProcessId,
50    signal::FetchableSignal,
51    signal_set::FetchableSignalSet,
52    user::Uid,
53};
54use iceoryx2_log::{fail, fatal_panic};
55use iceoryx2_pal_os_api::linux;
56use iceoryx2_pal_posix::posix::{self};
57
58/// Error emitted when creating a new [`SignalFd`].
59#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
60pub enum SignalFdCreationError {
61    /// The process wide file handle limit is reached
62    PerProcessFileHandleLimitReached,
63    /// The system wide file handle limit is reached
64    SystemWideFileHandleLimitReached,
65    /// Insufficient memory available
66    InsufficientMemory,
67    /// The underlying inode device could not be mounted
68    UnableToMountInodeDevice,
69    /// An error that was not documented in the POSIX API was reported
70    UnknownError(i32),
71}
72
73impl core::fmt::Display for SignalFdCreationError {
74    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
75        write!(f, "SignalFdCreationError::{self:?}")
76    }
77}
78
79impl core::error::Error for SignalFdCreationError {}
80
81/// Error emitted from [`BlockingSignalFd::blocking_read()`] or [`SignalFd::try_read()`].
82#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
83pub enum SignalFdReadError {
84    /// The amount of bytes read were less than the size of the internal siginfo struct
85    SystemBreaksReadContract,
86    /// An interrupt signal was raised
87    Interrupt,
88    /// An input/output error occurred
89    IOerror,
90    /// Insufficient resources available
91    InsufficientResources,
92    /// Insufficient memory available
93    InsufficientMemory,
94    /// An error that was not documented in the POSIX API was reported
95    UnknownError(i32),
96}
97
98impl core::fmt::Display for SignalFdReadError {
99    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
100        write!(f, "SignalFdReadError::{self:?}")
101    }
102}
103
104impl core::error::Error for SignalFdReadError {}
105
106/// The builder that creates a [`SignalFd`] or a [`BlockingSignalFd`].
107#[derive(Debug)]
108pub struct SignalFdBuilder {
109    signal_set: FetchableSignalSet,
110    close_on_exec: bool,
111}
112
113impl SignalFdBuilder {
114    /// Creates a new builder and the [`FetchableSignalSet`] defines which [`FetchableSignal`]s
115    /// the [`SignalFd`] shall receive.
116    pub fn new(signal_set: FetchableSignalSet) -> Self {
117        Self {
118            signal_set,
119            close_on_exec: false,
120        }
121    }
122
123    /// Defines if the underlying [`FileDescriptor`] shall be closed when the
124    /// [`Process`](iceoryx2_bb_posix::process::Process) is forked.
125    pub fn set_close_on_exec(mut self, value: bool) -> Self {
126        self.close_on_exec = value;
127        self
128    }
129
130    /// Create the non-blocking version of the [`SignalFd`].
131    pub fn create_non_blocking(self) -> Result<SignalFd, SignalFdCreationError> {
132        Ok(SignalFd {
133            file_descriptor: self.create(true)?,
134        })
135    }
136
137    /// Create the blocking version [`BlockingSignalFd`]
138    pub fn create_blocking(self) -> Result<BlockingSignalFd, SignalFdCreationError> {
139        Ok(BlockingSignalFd {
140            file_descriptor: self.create(false)?,
141        })
142    }
143
144    fn create(self, is_non_blocking: bool) -> Result<FileDescriptor, SignalFdCreationError> {
145        let msg = "Unable to create SignalFd";
146        let mut flags = 0;
147        if self.close_on_exec {
148            flags |= linux::SFD_CLOEXEC;
149        }
150
151        if is_non_blocking {
152            flags |= linux::SFD_NONBLOCK;
153        }
154
155        let fd = unsafe { linux::signalfd(-1, self.signal_set.native_handle(), flags as _) };
156
157        if fd == -1 {
158            match posix::Errno::get() {
159                posix::Errno::EMFILE => {
160                    fail!(from self,
161                        with SignalFdCreationError::PerProcessFileHandleLimitReached,
162                        "{msg} since the per process file descriptor limit is exceeded.");
163                }
164                posix::Errno::ENFILE => {
165                    fail!(from self,
166                        with SignalFdCreationError::SystemWideFileHandleLimitReached,
167                        "{msg} since the system wide file descriptor limit is exceeded.");
168                }
169                posix::Errno::ENODEV => {
170                    fail!(from self,
171                        with SignalFdCreationError::UnableToMountInodeDevice,
172                        "{msg} since anonymous inode device could not be mapped.");
173                }
174                posix::Errno::ENOMEM => {
175                    fail!(from self,
176                        with SignalFdCreationError::InsufficientMemory,
177                        "{msg} due to insufficient memory.");
178                }
179                e => {
180                    fail!(from self,
181                        with SignalFdCreationError::UnknownError(e as i32),
182                        "{msg} due to an unknown error {e:?}.");
183                }
184            }
185        }
186
187        let file_descriptor = match FileDescriptor::new(fd) {
188            Some(fd) => fd,
189            None => fatal_panic!(from self,
190                "This should never happen! {msg} since the signalfd returned a broken file descriptor (fd)."),
191        };
192
193        Ok(file_descriptor)
194    }
195}
196
197/// Contains all details about the [`FetchableSignal`] that was raised.
198pub struct SignalInfo {
199    signal_info: linux::signalfd_siginfo,
200}
201
202impl Debug for SignalInfo {
203    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
204        write!(
205            f,
206            "SignalInfo {{ signal: {:?}, origin_pid: {}, origin_uid: {} }}",
207            self.signal(),
208            self.origin_pid(),
209            self.origin_uid()
210        )
211    }
212}
213
214impl SignalInfo {
215    /// Returns the [`FetchableSignal`] that was received.
216    pub fn signal(&self) -> FetchableSignal {
217        (self.signal_info.ssi_signo as i32).into()
218    }
219
220    /// Returns the [`ProcessId`] of the origin that sent the signal.
221    pub fn origin_pid(&self) -> ProcessId {
222        ProcessId::new(self.signal_info.ssi_pid as _)
223    }
224
225    /// Returns the [`Uid`] of the origin that sent the signal.
226    pub fn origin_uid(&self) -> Uid {
227        Uid::new_from_native(self.signal_info.ssi_uid)
228    }
229}
230
231/// Non-blocking version of a signalfd
232#[derive(Debug)]
233pub struct SignalFd {
234    file_descriptor: FileDescriptor,
235}
236
237fn read_from_fd<T: Debug>(
238    this: &T,
239    fd: &FileDescriptor,
240) -> Result<Option<SignalInfo>, SignalFdReadError> {
241    let msg = "Unable to read signal from SignalFd";
242    let mut signal_info: linux::signalfd_siginfo = unsafe { core::mem::zeroed() };
243
244    let number_of_bytes = unsafe {
245        posix::read(
246            fd.native_handle(),
247            ((&mut signal_info) as *mut linux::signalfd_siginfo).cast(),
248            core::mem::size_of::<linux::signalfd_siginfo>(),
249        )
250    };
251
252    if number_of_bytes == core::mem::size_of::<linux::signalfd_siginfo>() as _ {
253        return Ok(Some(SignalInfo { signal_info }));
254    }
255
256    if number_of_bytes != -1 {
257        fail!(from this,
258            with SignalFdReadError::SystemBreaksReadContract,
259            "{msg} since only {number_of_bytes} bytes were read but {} bytes were expected. This breaks the contract with the system.",
260            core::mem::size_of::<linux::signalfd_siginfo>());
261    }
262
263    match posix::Errno::get() {
264        posix::Errno::EAGAIN => Ok(None),
265        posix::Errno::EINTR => {
266            fail!(from this,
267                with SignalFdReadError::Interrupt,
268                "{msg} since an interrupt signal was raised.");
269        }
270        posix::Errno::EIO => {
271            fail!(from this,
272                with SignalFdReadError::IOerror,
273                "{msg} due to an i/o error.");
274        }
275        posix::Errno::ENOBUFS => {
276            fail!(from this,
277                with SignalFdReadError::InsufficientResources,
278                "{msg} due insufficient resources.");
279        }
280        posix::Errno::ENOMEM => {
281            fail!(from this,
282                with SignalFdReadError::InsufficientMemory,
283                "{msg} due insufficient memory.");
284        }
285        e => {
286            fail!(from this,
287                with SignalFdReadError::UnknownError(e as _),
288                "{msg} due to an unknown error ({e:?}).");
289        }
290    }
291}
292
293impl SignalFd {
294    /// Tries to read the raised [`FetchableSignal`]. If no signal was raised it returns
295    /// [`None`].
296    pub fn try_read(&self) -> Result<Option<SignalInfo>, SignalFdReadError> {
297        read_from_fd(self, &self.file_descriptor)
298    }
299}
300
301impl FileDescriptorBased for SignalFd {
302    fn file_descriptor(&self) -> &FileDescriptor {
303        &self.file_descriptor
304    }
305}
306
307impl SynchronousMultiplexing for SignalFd {}
308
309/// Blocking version of the signal fd
310#[derive(Debug)]
311pub struct BlockingSignalFd {
312    file_descriptor: FileDescriptor,
313}
314
315impl BlockingSignalFd {
316    /// Blocks until either the raised [`FetchableSignal`] was received or an error was
317    /// reported. It might have spurious wake ups.
318    pub fn blocking_read(&self) -> Result<Option<SignalInfo>, SignalFdReadError> {
319        read_from_fd(self, &self.file_descriptor)
320    }
321}
322
323impl FileDescriptorBased for BlockingSignalFd {
324    fn file_descriptor(&self) -> &FileDescriptor {
325        &self.file_descriptor
326    }
327}
328
329impl SynchronousMultiplexing for BlockingSignalFd {}