Skip to main content

shrub_core/
traps.rs

1//! Facilities for configuring trap handlers.
2
3use std::collections::HashMap;
4use std::fmt::Display;
5use std::str::FromStr;
6
7use itertools::Itertools as _;
8
9use crate::{error, sys};
10
11/// Type of signal that can be trapped in the shell.
12#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
13pub enum TrapSignal {
14    /// A system signal.
15    Signal(sys::signal::Signal),
16    /// The `DEBUG` trap.
17    Debug,
18    /// The `ERR` trap.
19    Err,
20    /// The `EXIT` trap.
21    Exit,
22    /// The `RETURN` trp.
23    Return,
24}
25
26#[cfg(feature = "serde")]
27impl serde::Serialize for TrapSignal {
28    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
29    where
30        S: serde::Serializer,
31    {
32        serializer.serialize_str(self.as_str())
33    }
34}
35
36#[cfg(feature = "serde")]
37impl<'de> serde::Deserialize<'de> for TrapSignal {
38    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
39    where
40        D: serde::Deserializer<'de>,
41    {
42        let s = String::deserialize(deserializer)?;
43        Self::try_from(s.as_str()).map_err(serde::de::Error::custom)
44    }
45}
46
47impl Display for TrapSignal {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        f.write_str(self.as_str())
50    }
51}
52
53impl TrapSignal {
54    /// Returns all possible values of [`TrapSignal`].
55    pub fn iterator() -> impl Iterator<Item = Self> {
56        const SIGNALS: &[TrapSignal] = &[TrapSignal::Debug, TrapSignal::Err, TrapSignal::Exit];
57
58        let iter = itertools::chain!(
59            SIGNALS.iter().copied(),
60            sys::signal::Signal::iterator().map(TrapSignal::Signal)
61        );
62
63        iter
64    }
65
66    /// Converts [`TrapSignal`] into its corresponding signal name as a
67    /// [`&'static str`](str)
68    pub const fn as_str(self) -> &'static str {
69        match self {
70            Self::Signal(s) => s.as_str(),
71            Self::Debug => "DEBUG",
72            Self::Err => "ERR",
73            Self::Exit => "EXIT",
74            Self::Return => "RETURN",
75        }
76    }
77}
78
79/// Formats [`Iterator<Item = TrapSignal>`](TrapSignal)  to the provided writer.
80///
81/// # Arguments
82///
83/// * `f` - Any type that implements [`std::io::Write`].
84/// * `it` - An iterator over the signals that will be formatted into the `f`.
85///
86/// # Errors
87///
88/// Returns [`crate::error::ErrorKind::IoError`] if writing to `f` fails.
89pub fn format_signals(
90    mut f: impl std::io::Write,
91    it: impl Iterator<Item = TrapSignal>,
92) -> Result<(), error::Error> {
93    let it = it
94        .filter_map(|s| i32::try_from(s).ok().map(|n| (s, n)))
95        .sorted_by(|a, b| Ord::cmp(&a.1, &b.1))
96        .format_with("\n", |s, f| f(&format_args!("{}) {}", s.1, s.0)));
97    write!(f, "{it}")?;
98    Ok(())
99}
100
101// implement s.parse::<TrapSignal>()
102impl FromStr for TrapSignal {
103    type Err = error::Error;
104    fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
105        if let Ok(n) = s.parse::<i32>() {
106            Self::try_from(n)
107        } else {
108            Self::try_from(s)
109        }
110    }
111}
112
113// from a signal number
114impl TryFrom<i32> for TrapSignal {
115    type Error = error::Error;
116    fn try_from(value: i32) -> Result<Self, Self::Error> {
117        // NOTE: DEBUG and ERR are real-time signals, defined based on NSIG or
118        // SIGRTMAX (is not available on bsd-like systems),
119        // and don't have persistent numbers across platforms, so we skip them
120        // here.
121        Ok(match value {
122            0 => Self::Exit,
123            value => Self::Signal(
124                sys::signal::Signal::try_from(value)
125                    .map_err(|_| error::ErrorKind::InvalidSignal(value.to_string()))?,
126            ),
127        })
128    }
129}
130
131// from a signal name
132impl TryFrom<&str> for TrapSignal {
133    type Error = error::Error;
134    fn try_from(value: &str) -> Result<Self, Self::Error> {
135        #[allow(unused_mut, reason = "only mutated on some platforms")]
136        let mut s = value.to_ascii_uppercase();
137
138        Ok(match s.as_str() {
139            "DEBUG" => Self::Debug,
140            "ERR" => Self::Err,
141            "EXIT" => Self::Exit,
142            "RETURN" => Self::Return,
143            _ => {
144                // Bash compatibility:
145                // support for signal names without the `SIG` prefix, for
146                // example `HUP` -> `SIGHUP`
147                if !s.starts_with("SIG") {
148                    s.insert_str(0, "SIG");
149                }
150                sys::signal::Signal::from_str(s.as_str())
151                    .map(TrapSignal::Signal)
152                    .map_err(|_| error::ErrorKind::InvalidSignal(value.into()))?
153            }
154        })
155    }
156}
157
158/// Error type used when failing to convert a `TrapSignal` to a number.
159#[derive(Debug, Clone, Copy)]
160pub struct TrapSignalNumberError;
161
162impl TryFrom<TrapSignal> for i32 {
163    type Error = TrapSignalNumberError;
164    fn try_from(value: TrapSignal) -> Result<Self, Self::Error> {
165        Ok(match value {
166            TrapSignal::Signal(s) => s as Self,
167            TrapSignal::Exit => 0,
168            _ => return Err(TrapSignalNumberError),
169        })
170    }
171}
172
173/// A handler for a trap signal.
174#[derive(Clone, Default)]
175#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
176pub struct TrapHandler {
177    /// The source text of the command to invoke.
178    pub command: String,
179    /// Source information for where the trap handler was defined.
180    pub source_info: crate::SourceInfo,
181}
182
183/// Configuration for trap handlers in the shell.
184#[derive(Clone, Default)]
185#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
186pub struct TrapHandlerConfig {
187    /// Registered handlers for traps; maps signal type to command.
188    handlers: HashMap<TrapSignal, TrapHandler>,
189}
190
191impl TrapHandlerConfig {
192    /// Iterates over the registered handlers for trap signals.
193    pub fn iter_handlers(&self) -> impl Iterator<Item = (TrapSignal, &TrapHandler)> {
194        self.handlers
195            .iter()
196            .map(|(signal, handler)| (*signal, handler))
197    }
198
199    /// Tries to find the handler associated with the given signal.
200    ///
201    /// # Arguments
202    ///
203    /// * `signal_type` - The type of signal to get the handler for.
204    pub fn get_handler(&self, signal_type: TrapSignal) -> Option<&TrapHandler> {
205        self.handlers.get(&signal_type)
206    }
207
208    /// Returns whether a handler is registered for the given signal.
209    pub fn handles(&self, signal_type: TrapSignal) -> bool {
210        self.handlers.contains_key(&signal_type)
211    }
212
213    /// Registers a handler for a trap signal.
214    ///
215    /// # Arguments
216    ///
217    /// * `signal_type` - The type of signal to register a handler for.
218    /// * `command` - The command to execute when the signal is trapped.
219    /// * `source_info` - The source info for where the trap handler was
220    ///   defined.
221    pub fn register_handler(
222        &mut self,
223        signal_type: TrapSignal,
224        command: String,
225        source_info: crate::SourceInfo,
226    ) {
227        let _ = self.handlers.insert(
228            signal_type,
229            TrapHandler {
230                command,
231                source_info,
232            },
233        );
234    }
235
236    /// Removes handlers for a trap signal.
237    ///
238    /// # Arguments
239    ///
240    /// * `signal_type` - The type of signal to remove handlers for.
241    pub fn remove_handlers(&mut self, signal_type: TrapSignal) {
242        self.handlers.remove(&signal_type);
243    }
244}