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`.
85pub fn format_signals(
86    mut f: impl std::io::Write,
87    it: impl Iterator<Item = TrapSignal>,
88) -> Result<(), error::Error> {
89    let it = it
90        .filter_map(|s| i32::try_from(s).ok().map(|n| (s, n)))
91        .sorted_by(|a, b| Ord::cmp(&a.1, &b.1))
92        .format_with("\n", |s, f| f(&format_args!("{}) {}", s.1, s.0)));
93    write!(f, "{it}")?;
94    Ok(())
95}
96
97// implement s.parse::<TrapSignal>()
98impl FromStr for TrapSignal {
99    type Err = error::Error;
100    fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
101        if let Ok(n) = s.parse::<i32>() {
102            Self::try_from(n)
103        } else {
104            Self::try_from(s)
105        }
106    }
107}
108
109// from a signal number
110impl TryFrom<i32> for TrapSignal {
111    type Error = error::Error;
112    fn try_from(value: i32) -> Result<Self, Self::Error> {
113        // NOTE: DEBUG and ERR are real-time signals, defined based on NSIG or
114        // SIGRTMAX (is not available on bsd-like systems),
115        // and don't have persistent numbers across platforms, so we skip them
116        // here.
117        Ok(match value {
118            0 => Self::Exit,
119            value => Self::Signal(
120                sys::signal::Signal::try_from(value)
121                    .map_err(|_| error::ErrorKind::InvalidSignal(value.to_string()))?,
122            ),
123        })
124    }
125}
126
127// from a signal name
128impl TryFrom<&str> for TrapSignal {
129    type Error = error::Error;
130    fn try_from(value: &str) -> Result<Self, Self::Error> {
131        #[allow(unused_mut, reason = "only mutated on some platforms")]
132        let mut s = value.to_ascii_uppercase();
133
134        Ok(match s.as_str() {
135            "DEBUG" => Self::Debug,
136            "ERR" => Self::Err,
137            "EXIT" => Self::Exit,
138            "RETURN" => Self::Return,
139            _ => {
140                // Bash compatibility:
141                // support for signal names without the `SIG` prefix, for
142                // example `HUP` -> `SIGHUP`
143                if !s.starts_with("SIG") {
144                    s.insert_str(0, "SIG");
145                }
146                sys::signal::Signal::from_str(s.as_str())
147                    .map(TrapSignal::Signal)
148                    .map_err(|_| error::ErrorKind::InvalidSignal(value.into()))?
149            }
150        })
151    }
152}
153
154/// Error type used when failing to convert a `TrapSignal` to a number.
155#[derive(Debug, Clone, Copy)]
156pub struct TrapSignalNumberError;
157
158impl TryFrom<TrapSignal> for i32 {
159    type Error = TrapSignalNumberError;
160    fn try_from(value: TrapSignal) -> Result<Self, Self::Error> {
161        Ok(match value {
162            TrapSignal::Signal(s) => s as Self,
163            TrapSignal::Exit => 0,
164            _ => return Err(TrapSignalNumberError),
165        })
166    }
167}
168
169/// A handler for a trap signal.
170#[derive(Clone, Default)]
171#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
172pub struct TrapHandler {
173    /// The source text of the command to invoke.
174    pub command: String,
175    /// Source information for where the trap handler was defined.
176    pub source_info: crate::SourceInfo,
177}
178
179/// Configuration for trap handlers in the shell.
180#[derive(Clone, Default)]
181#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
182pub struct TrapHandlerConfig {
183    /// Registered handlers for traps; maps signal type to command.
184    handlers: HashMap<TrapSignal, TrapHandler>,
185}
186
187impl TrapHandlerConfig {
188    /// Iterates over the registered handlers for trap signals.
189    pub fn iter_handlers(&self) -> impl Iterator<Item = (TrapSignal, &TrapHandler)> {
190        self.handlers
191            .iter()
192            .map(|(signal, handler)| (*signal, handler))
193    }
194
195    /// Tries to find the handler associated with the given signal.
196    ///
197    /// # Arguments
198    ///
199    /// * `signal_type` - The type of signal to get the handler for.
200    pub fn get_handler(&self, signal_type: TrapSignal) -> Option<&TrapHandler> {
201        self.handlers.get(&signal_type)
202    }
203
204    /// Returns whether a handler is registered for the given signal.
205    pub fn handles(&self, signal_type: TrapSignal) -> bool {
206        self.handlers.contains_key(&signal_type)
207    }
208
209    /// Registers a handler for a trap signal.
210    ///
211    /// # Arguments
212    ///
213    /// * `signal_type` - The type of signal to register a handler for.
214    /// * `command` - The command to execute when the signal is trapped.
215    /// * `source_info` - The source info for where the trap handler was
216    ///   defined.
217    pub fn register_handler(
218        &mut self,
219        signal_type: TrapSignal,
220        command: String,
221        source_info: crate::SourceInfo,
222    ) {
223        let _ = self.handlers.insert(
224            signal_type,
225            TrapHandler {
226                command,
227                source_info,
228            },
229        );
230    }
231
232    /// Removes handlers for a trap signal.
233    ///
234    /// # Arguments
235    ///
236    /// * `signal_type` - The type of signal to remove handlers for.
237    pub fn remove_handlers(&mut self, signal_type: TrapSignal) {
238        self.handlers.remove(&signal_type);
239    }
240}