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}