Skip to main content

yash_env/system/
signal.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2025 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Signal-related functionality for the system module
18
19#[cfg(doc)]
20use super::SharedSystem;
21use super::{Pid, Result};
22pub use crate::signal::{Name, Number, RawNumber};
23use std::borrow::Cow;
24use std::num::NonZero;
25use std::ops::RangeInclusive;
26
27/// Trait for managing available signals
28pub trait Signals {
29    /// The signal number for `SIGABRT`
30    const SIGABRT: Number;
31    /// The signal number for `SIGALRM`
32    const SIGALRM: Number;
33    /// The signal number for `SIGBUS`
34    const SIGBUS: Number;
35    /// The signal number for `SIGCHLD`
36    const SIGCHLD: Number;
37    /// The signal number for `SIGCLD`, if available on the system
38    const SIGCLD: Option<Number>;
39    /// The signal number for `SIGCONT`
40    const SIGCONT: Number;
41    /// The signal number for `SIGEMT`, if available on the system
42    const SIGEMT: Option<Number>;
43    /// The signal number for `SIGFPE`
44    const SIGFPE: Number;
45    /// The signal number for `SIGHUP`
46    const SIGHUP: Number;
47    /// The signal number for `SIGILL`
48    const SIGILL: Number;
49    /// The signal number for `SIGINFO`, if available on the system
50    const SIGINFO: Option<Number>;
51    /// The signal number for `SIGINT`
52    const SIGINT: Number;
53    /// The signal number for `SIGIO`, if available on the system
54    const SIGIO: Option<Number>;
55    /// The signal number for `SIGIOT`
56    const SIGIOT: Number;
57    /// The signal number for `SIGKILL`
58    const SIGKILL: Number;
59    /// The signal number for `SIGLOST`, if available on the system
60    const SIGLOST: Option<Number>;
61    /// The signal number for `SIGPIPE`
62    const SIGPIPE: Number;
63    /// The signal number for `SIGPOLL`, if available on the system
64    const SIGPOLL: Option<Number>;
65    /// The signal number for `SIGPROF`
66    const SIGPROF: Number;
67    /// The signal number for `SIGPWR`, if available on the system
68    const SIGPWR: Option<Number>;
69    /// The signal number for `SIGQUIT`
70    const SIGQUIT: Number;
71    /// The signal number for `SIGSEGV`
72    const SIGSEGV: Number;
73    /// The signal number for `SIGSTKFLT`, if available on the system
74    const SIGSTKFLT: Option<Number>;
75    /// The signal number for `SIGSTOP`
76    const SIGSTOP: Number;
77    /// The signal number for `SIGSYS`
78    const SIGSYS: Number;
79    /// The signal number for `SIGTERM`
80    const SIGTERM: Number;
81    /// The signal number for `SIGTHR`, if available on the system
82    const SIGTHR: Option<Number>;
83    /// The signal number for `SIGTRAP`
84    const SIGTRAP: Number;
85    /// The signal number for `SIGTSTP`
86    const SIGTSTP: Number;
87    /// The signal number for `SIGTTIN`
88    const SIGTTIN: Number;
89    /// The signal number for `SIGTTOU`
90    const SIGTTOU: Number;
91    /// The signal number for `SIGURG`
92    const SIGURG: Number;
93    /// The signal number for `SIGUSR1`
94    const SIGUSR1: Number;
95    /// The signal number for `SIGUSR2`
96    const SIGUSR2: Number;
97    /// The signal number for `SIGVTALRM`
98    const SIGVTALRM: Number;
99    /// The signal number for `SIGWINCH`
100    const SIGWINCH: Number;
101    /// The signal number for `SIGXCPU`
102    const SIGXCPU: Number;
103    /// The signal number for `SIGXFSZ`
104    const SIGXFSZ: Number;
105
106    /// Returns the range of real-time signals supported by the system.
107    ///
108    /// If the system does not support real-time signals, returns `None`.
109    ///
110    /// The range is provided as a method rather than associated constants
111    /// because some systems determine the range at runtime.
112    #[must_use]
113    fn sigrt_range(&self) -> Option<RangeInclusive<Number>>;
114
115    /// List of all signal names and their numbers, excluding real-time signals
116    ///
117    /// This list contains all named signals declared in this trait, except for
118    /// real-time signals. Each entry is a tuple of the signal name (without the
119    /// `SIG` prefix) and its corresponding signal number. If a signal is not
120    /// available on the system, its number is `None`.
121    ///
122    /// The signals are listed in alphabetical order by name (without the `SIG`
123    /// prefix). Implementations that override this constant must preserve this
124    /// ordering because the default implementation of
125    /// [`str2sig`](Self::str2sig) relies on it to perform a binary search.
126    const NAMED_SIGNALS: &'static [(&'static str, Option<Number>)] = &[
127        ("ABRT", Some(Self::SIGABRT)),
128        ("ALRM", Some(Self::SIGALRM)),
129        ("BUS", Some(Self::SIGBUS)),
130        ("CHLD", Some(Self::SIGCHLD)),
131        ("CLD", Self::SIGCLD),
132        ("CONT", Some(Self::SIGCONT)),
133        ("EMT", Self::SIGEMT),
134        ("FPE", Some(Self::SIGFPE)),
135        ("HUP", Some(Self::SIGHUP)),
136        ("ILL", Some(Self::SIGILL)),
137        ("INFO", Self::SIGINFO),
138        ("INT", Some(Self::SIGINT)),
139        ("IO", Self::SIGIO),
140        ("IOT", Some(Self::SIGIOT)),
141        ("KILL", Some(Self::SIGKILL)),
142        ("LOST", Self::SIGLOST),
143        ("PIPE", Some(Self::SIGPIPE)),
144        ("POLL", Self::SIGPOLL),
145        ("PROF", Some(Self::SIGPROF)),
146        ("PWR", Self::SIGPWR),
147        ("QUIT", Some(Self::SIGQUIT)),
148        ("SEGV", Some(Self::SIGSEGV)),
149        ("STKFLT", Self::SIGSTKFLT),
150        ("STOP", Some(Self::SIGSTOP)),
151        ("SYS", Some(Self::SIGSYS)),
152        ("TERM", Some(Self::SIGTERM)),
153        ("THR", Self::SIGTHR),
154        ("TRAP", Some(Self::SIGTRAP)),
155        ("TSTP", Some(Self::SIGTSTP)),
156        ("TTIN", Some(Self::SIGTTIN)),
157        ("TTOU", Some(Self::SIGTTOU)),
158        ("URG", Some(Self::SIGURG)),
159        ("USR1", Some(Self::SIGUSR1)),
160        ("USR2", Some(Self::SIGUSR2)),
161        ("VTALRM", Some(Self::SIGVTALRM)),
162        ("WINCH", Some(Self::SIGWINCH)),
163        ("XCPU", Some(Self::SIGXCPU)),
164        ("XFSZ", Some(Self::SIGXFSZ)),
165    ];
166
167    /// Returns an iterator over all real-time signals supported by the system.
168    ///
169    /// The iterator yields signal numbers in ascending order. If the system
170    /// does not support real-time signals, the iterator yields no items.
171    fn iter_sigrt(&self) -> impl DoubleEndedIterator<Item = Number> + use<Self> {
172        let range = match self.sigrt_range() {
173            Some(range) => range.start().as_raw()..=range.end().as_raw(),
174            #[allow(clippy::reversed_empty_ranges)]
175            None => 0..=-1,
176        };
177        // If NonZero implemented Step, we could use range.map(...)
178        range.filter_map(|raw| NonZero::new(raw).map(Number::from_raw_unchecked))
179    }
180
181    /// Tests if a signal number is valid and returns its signal number.
182    ///
183    /// This function returns `Some(number)` if the signal number refers to a valid
184    /// signal supported by the system. Otherwise, it returns `None`.
185    #[must_use]
186    fn to_signal_number<N: Into<RawNumber>>(&self, number: N) -> Option<Number> {
187        fn inner<S: Signals + ?Sized>(system: &S, raw_number: RawNumber) -> Option<Number> {
188            let non_zero = NonZero::new(raw_number)?;
189            let number = Number::from_raw_unchecked(non_zero);
190            (S::NAMED_SIGNALS
191                .iter()
192                .any(|signal| signal.1 == Some(number))
193                || system
194                    .sigrt_range()
195                    .is_some_and(|range| range.contains(&number)))
196            .then_some(number)
197        }
198        inner(self, number.into())
199    }
200
201    /// Converts a signal number to its string representation.
202    ///
203    /// This function returns `Some(name)` if the signal number refers to a valid
204    /// signal supported by the system. Otherwise, it returns `None`.
205    ///
206    /// The returned name does not include the `SIG` prefix.
207    /// Note that one signal number can have multiple names, in which case it is
208    /// unspecified which name is returned.
209    #[must_use]
210    fn sig2str<N: Into<RawNumber>>(&self, signal: N) -> Option<Cow<'static, str>> {
211        fn inner<S: Signals + ?Sized>(
212            system: &S,
213            raw_number: RawNumber,
214        ) -> Option<Cow<'static, str>> {
215            let number = Number::from_raw_unchecked(NonZero::new(raw_number)?);
216            // The signals below are ordered roughly by frequency of use
217            // so that common names are preferred for signals with multiple names.
218            match () {
219                () if number == S::SIGABRT => Some(Cow::Borrowed("ABRT")),
220                () if number == S::SIGALRM => Some(Cow::Borrowed("ALRM")),
221                () if number == S::SIGBUS => Some(Cow::Borrowed("BUS")),
222                () if number == S::SIGCHLD => Some(Cow::Borrowed("CHLD")),
223                () if number == S::SIGCONT => Some(Cow::Borrowed("CONT")),
224                () if number == S::SIGFPE => Some(Cow::Borrowed("FPE")),
225                () if number == S::SIGHUP => Some(Cow::Borrowed("HUP")),
226                () if number == S::SIGILL => Some(Cow::Borrowed("ILL")),
227                () if number == S::SIGINT => Some(Cow::Borrowed("INT")),
228                () if number == S::SIGKILL => Some(Cow::Borrowed("KILL")),
229                () if number == S::SIGPIPE => Some(Cow::Borrowed("PIPE")),
230                () if number == S::SIGQUIT => Some(Cow::Borrowed("QUIT")),
231                () if number == S::SIGSEGV => Some(Cow::Borrowed("SEGV")),
232                () if number == S::SIGSTOP => Some(Cow::Borrowed("STOP")),
233                () if number == S::SIGTERM => Some(Cow::Borrowed("TERM")),
234                () if number == S::SIGTSTP => Some(Cow::Borrowed("TSTP")),
235                () if number == S::SIGTTIN => Some(Cow::Borrowed("TTIN")),
236                () if number == S::SIGTTOU => Some(Cow::Borrowed("TTOU")),
237                () if number == S::SIGUSR1 => Some(Cow::Borrowed("USR1")),
238                () if number == S::SIGUSR2 => Some(Cow::Borrowed("USR2")),
239                () if Some(number) == S::SIGPOLL => Some(Cow::Borrowed("POLL")),
240                () if number == S::SIGPROF => Some(Cow::Borrowed("PROF")),
241                () if number == S::SIGSYS => Some(Cow::Borrowed("SYS")),
242                () if number == S::SIGTRAP => Some(Cow::Borrowed("TRAP")),
243                () if number == S::SIGURG => Some(Cow::Borrowed("URG")),
244                () if number == S::SIGVTALRM => Some(Cow::Borrowed("VTALRM")),
245                () if number == S::SIGWINCH => Some(Cow::Borrowed("WINCH")),
246                () if number == S::SIGXCPU => Some(Cow::Borrowed("XCPU")),
247                () if number == S::SIGXFSZ => Some(Cow::Borrowed("XFSZ")),
248                () if Some(number) == S::SIGEMT => Some(Cow::Borrowed("EMT")),
249                () if Some(number) == S::SIGINFO => Some(Cow::Borrowed("INFO")),
250                () if Some(number) == S::SIGIO => Some(Cow::Borrowed("IO")),
251                () if Some(number) == S::SIGLOST => Some(Cow::Borrowed("LOST")),
252                () if Some(number) == S::SIGPWR => Some(Cow::Borrowed("PWR")),
253                () if Some(number) == S::SIGSTKFLT => Some(Cow::Borrowed("STKFLT")),
254                () if Some(number) == S::SIGTHR => Some(Cow::Borrowed("THR")),
255                _ => {
256                    let range = system.sigrt_range()?;
257                    if number == *range.start() {
258                        Some(Cow::Borrowed("RTMIN"))
259                    } else if number == *range.end() {
260                        Some(Cow::Borrowed("RTMAX"))
261                    } else if range.contains(&number) {
262                        let rtmin = range.start().as_raw();
263                        let rtmax = range.end().as_raw();
264                        if raw_number <= rtmin.midpoint(rtmax) {
265                            let offset = raw_number - rtmin;
266                            Some(Cow::Owned(format!("RTMIN+{}", offset)))
267                        } else {
268                            let offset = rtmax - raw_number;
269                            Some(Cow::Owned(format!("RTMAX-{}", offset)))
270                        }
271                    } else {
272                        None
273                    }
274                }
275            }
276        }
277        inner(self, signal.into())
278    }
279
280    /// Converts a string representation of a signal to its signal number.
281    ///
282    /// This function returns `Some(number)` if the signal name is supported by
283    /// the system. Otherwise, it returns `None`.
284    ///
285    /// The input name should not include the `SIG` prefix, and is case-sensitive.
286    #[must_use]
287    fn str2sig(&self, name: &str) -> Option<Number> {
288        // Binary search on NAMED_SIGNALS
289        if let Ok(index) = Self::NAMED_SIGNALS.binary_search_by_key(&name, |s| s.0) {
290            return Self::NAMED_SIGNALS[index].1;
291        }
292
293        // Handle real-time signals
294        enum BaseName {
295            Rtmin,
296            Rtmax,
297        }
298        let (basename, suffix) = if let Some(suffix) = name.strip_prefix("RTMIN") {
299            (BaseName::Rtmin, suffix)
300        } else if let Some(suffix) = name.strip_prefix("RTMAX") {
301            (BaseName::Rtmax, suffix)
302        } else {
303            return None;
304        };
305        if !suffix.is_empty() && !suffix.starts_with(['+', '-']) {
306            return None;
307        }
308        let range = self.sigrt_range()?;
309        let base_raw = match basename {
310            BaseName::Rtmin => range.start().as_raw(),
311            BaseName::Rtmax => range.end().as_raw(),
312        };
313        let raw_number = if suffix.is_empty() {
314            base_raw
315        } else {
316            let offset: RawNumber = suffix.parse().ok()?;
317            base_raw.checked_add(offset)?
318        };
319        let number = Number::from_raw_unchecked(NonZero::new(raw_number)?);
320        range.contains(&number).then_some(number)
321    }
322
323    /// Tests if a signal number is valid and returns its name and number.
324    ///
325    /// This function returns `Some((name, number))` if the signal number refers
326    /// to a valid signal supported by the system. Otherwise, it returns `None`.
327    ///
328    /// Note that one signal number can have multiple names, in which case this
329    /// function returns the name that is considered the most common.
330    ///
331    /// If you only need to tell whether a signal number is valid, use
332    /// [`to_signal_number`](Self::to_signal_number), which is more efficient.
333    #[must_use]
334    fn validate_signal(&self, number: RawNumber) -> Option<(Name, Number)> {
335        let number = Number::from_raw_unchecked(NonZero::new(number)?);
336        let str_name = self.sig2str(number)?;
337        Some((str_name.parse().ok()?, number))
338    }
339
340    /// Returns the signal name for the signal number.
341    ///
342    /// This function returns the signal name for the given signal number.
343    ///
344    /// If the signal number is invalid, this function panics. It may occur if
345    /// the number is from a different system or was created without checking
346    /// the validity.
347    ///
348    /// Note that one signal number can have multiple names, in which case this
349    /// function returns the name that is considered the most common.
350    #[must_use]
351    fn signal_name_from_number(&self, number: Number) -> Name {
352        self.validate_signal(number.as_raw()).unwrap().0
353    }
354
355    /// Gets the signal number from the signal name.
356    ///
357    /// This function returns the signal number corresponding to the signal name
358    /// in the system. If the signal name is not supported, it returns `None`.
359    #[must_use]
360    fn signal_number_from_name(&self, name: Name) -> Option<Number> {
361        self.str2sig(&name.as_string())
362    }
363}
364
365/// Operation applied to the signal blocking mask
366///
367/// This enum corresponds to the operations of the `sigprocmask` system call and
368/// is used in the [`Sigmask::sigmask`] method.
369#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
370#[non_exhaustive]
371pub enum SigmaskOp {
372    /// Add signals to the mask (`SIG_BLOCK`)
373    Add,
374    /// Remove signals from the mask (`SIG_UNBLOCK`)
375    Remove,
376    /// Set the mask to the given signals (`SIG_SETMASK`)
377    Set,
378}
379
380/// Trait for managing signal blocking mask
381pub trait Sigmask: Signals {
382    /// Gets and/or sets the signal blocking mask.
383    ///
384    /// This is a low-level function used internally by [`SharedSystem`]. You
385    /// should not call this function directly, or you will disrupt the behavior
386    /// of `SharedSystem`. The description below applies if you want to do
387    /// everything yourself without depending on `SharedSystem`.
388    ///
389    /// This is a thin wrapper around the [`sigprocmask` system
390    /// call](https://pubs.opengroup.org/onlinepubs/9799919799/functions/pthread_sigmask.html).
391    /// If `op` is `Some`, this function updates the signal blocking mask by
392    /// applying the given `SigmaskOp` and signal set to the current mask. If
393    /// `op` is `None`, this function does not change the mask.
394    /// If `old_mask` is `Some`, this function sets the previous mask to it.
395    fn sigmask(
396        &self,
397        op: Option<(SigmaskOp, &[Number])>,
398        old_mask: Option<&mut Vec<Number>>,
399    ) -> Result<()>;
400}
401
402/// How the shell process responds to a signal
403#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
404pub enum Disposition {
405    /// Perform the default action for the signal.
406    ///
407    /// The default action depends on the signal. For example, `SIGINT` causes
408    /// the process to terminate, and `SIGTSTP` causes the process to stop.
409    #[default]
410    Default,
411    /// Ignore the signal.
412    Ignore,
413    /// Catch the signal.
414    Catch,
415}
416
417/// Trait for getting signal dispositions
418pub trait GetSigaction: Signals {
419    /// Gets the disposition for a signal.
420    ///
421    /// This is a low-level function used internally by
422    /// [`SharedSystem`]. You should not call this function directly, or you
423    /// will leave the `SharedSystem` instance in an inconsistent state. The
424    /// description below applies if you want to do everything yourself without
425    /// depending on `SharedSystem`.
426    ///
427    /// This is an abstract wrapper around the [`sigaction` system
428    /// call](https://pubs.opengroup.org/onlinepubs/9799919799/functions/sigaction.html).
429    /// This function returns the current disposition if successful.
430    ///
431    /// To change the disposition, use [`Sigaction::sigaction`].
432    fn get_sigaction(&self, signal: Number) -> Result<Disposition>;
433}
434
435/// Trait for managing signal dispositions
436pub trait Sigaction: GetSigaction {
437    /// Gets and sets the disposition for a signal.
438    ///
439    /// This is a low-level function used internally by [`SharedSystem`]. You
440    /// should not call this function directly, or you will leave the
441    /// `SharedSystem` instance in an inconsistent state. The description below
442    /// applies if you want to do everything yourself without depending on
443    /// `SharedSystem`.
444    ///
445    /// This is an abstract wrapper around the [`sigaction` system
446    /// call](https://pubs.opengroup.org/onlinepubs/9799919799/functions/sigaction.html).
447    /// This function returns the previous disposition if successful.
448    ///
449    /// When you set the disposition to `Disposition::Catch`, signals sent to
450    /// this process are accumulated in `self` and made available from
451    /// [`caught_signals`](CaughtSignals::caught_signals).
452    ///
453    /// To get the current disposition without changing it, use
454    /// [`GetSigaction::get_sigaction`].
455    fn sigaction(&self, signal: Number, action: Disposition) -> Result<Disposition>;
456}
457
458/// Trait for examining signals caught by the process
459///
460/// Implementors of this trait usually also implement [`Sigaction`] to allow
461/// setting which signals are caught.
462pub trait CaughtSignals: Signals {
463    /// Returns signals this process has caught, if any.
464    ///
465    /// This is a low-level function used internally by
466    /// [`SharedSystem::select`]. You should not call this function directly, or
467    /// you will disrupt the behavior of `SharedSystem`. The description below
468    /// applies if you want to do everything yourself without depending on
469    /// `SharedSystem`.
470    ///
471    /// Implementors of this trait usually also implement [`Sigaction`] to allow
472    /// setting which signals are caught.
473    /// To catch a signal, you firstly install a signal handler by calling
474    /// [`Sigaction::sigaction`] with [`Disposition::Catch`]. Once the handler
475    /// is ready, signals sent to the process are accumulated in the
476    /// implementor. Calling this function retrieves the list of caught signals.
477    ///
478    /// This function clears the internal list of caught signals, so a next call
479    /// will return an empty list unless another signal is caught since the
480    /// first call. Because the list size may be limited, you should call this
481    /// function periodically before the list gets full, in which case further
482    /// caught signals are silently ignored.
483    ///
484    /// Note that signals become pending if sent while blocked by
485    /// [`Sigmask::sigmask`]. They must be unblocked so that they are caught and
486    /// made available from this function.
487    fn caught_signals(&self) -> Vec<Number>;
488}
489
490/// Trait for sending signals to processes
491pub trait SendSignal: Signals {
492    /// Sends a signal.
493    ///
494    /// This is a thin wrapper around the [`kill` system
495    /// call](https://pubs.opengroup.org/onlinepubs/9799919799/functions/kill.html).
496    /// If `signal` is `None`, permission to send a signal is checked, but no
497    /// signal is sent.
498    ///
499    /// The virtual system version of this function blocks the calling thread if
500    /// the signal stops or terminates the current process, hence returning a
501    /// future. See [`VirtualSystem::kill`] for details.
502    ///
503    /// [`VirtualSystem::kill`]: crate::system::virtual::VirtualSystem::kill
504    fn kill(
505        &self,
506        target: Pid,
507        signal: Option<Number>,
508    ) -> impl Future<Output = Result<()>> + use<Self>;
509
510    /// Sends a signal to the current process.
511    ///
512    /// This is a thin wrapper around the `raise` system call.
513    ///
514    /// The virtual system version of this function blocks the calling thread if
515    /// the signal stops or terminates the current process, hence returning a
516    /// future. See [`VirtualSystem::kill`] for details.
517    ///
518    /// [`VirtualSystem::kill`]: crate::system::virtual::VirtualSystem::kill
519    fn raise(&self, signal: Number) -> impl Future<Output = Result<()>> + use<Self>;
520}