1use core::fmt;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21#[non_exhaustive]
22pub enum Signal {
23 Terminate,
25 Interrupt,
27 Quit,
29 Hangup,
31 Pipe,
33 User1,
35 User2,
37}
38
39impl Signal {
40 pub const ALL: [Self; 7] = [
42 Self::Terminate,
43 Self::Interrupt,
44 Self::Quit,
45 Self::Hangup,
46 Self::Pipe,
47 Self::User1,
48 Self::User2,
49 ];
50
51 #[must_use]
53 pub const fn description(self) -> &'static str {
54 match self {
55 Self::Terminate => "Terminate (SIGTERM / CTRL_CLOSE_EVENT)",
56 Self::Interrupt => "Interrupt (SIGINT / CTRL_C_EVENT)",
57 Self::Quit => "Quit (SIGQUIT / CTRL_BREAK_EVENT)",
58 Self::Hangup => "Hangup (SIGHUP / CTRL_SHUTDOWN_EVENT)",
59 Self::Pipe => "Pipe (SIGPIPE, Unix only)",
60 Self::User1 => "User1 (SIGUSR1, Unix only)",
61 Self::User2 => "User2 (SIGUSR2, Unix only)",
62 }
63 }
64
65 #[must_use]
69 pub const fn unix_number(self) -> Option<i32> {
70 match self {
71 Self::Hangup => Some(1),
72 Self::Interrupt => Some(2),
73 Self::Quit => Some(3),
74 Self::User1 => Some(10),
75 Self::User2 => Some(12),
76 Self::Pipe => Some(13),
77 Self::Terminate => Some(15),
78 }
79 }
80
81 #[must_use]
83 pub const fn is_unix_only(self) -> bool {
84 matches!(self, Self::Pipe | Self::User1 | Self::User2)
85 }
86
87 #[must_use]
90 pub const fn available_on_current_platform(self) -> bool {
91 if cfg!(unix) {
92 true
93 } else {
94 !self.is_unix_only()
95 }
96 }
97
98 pub(crate) const fn bit(self) -> u16 {
99 match self {
100 Self::Terminate => 1 << 0,
101 Self::Interrupt => 1 << 1,
102 Self::Quit => 1 << 2,
103 Self::Hangup => 1 << 3,
104 Self::Pipe => 1 << 4,
105 Self::User1 => 1 << 5,
106 Self::User2 => 1 << 6,
107 }
108 }
109}
110
111impl fmt::Display for Signal {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 f.write_str(self.description())
114 }
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub struct SignalSet {
124 bits: u16,
125}
126
127impl SignalSet {
128 const ALL_BITS: u16 = 0b0111_1111;
130
131 #[must_use]
133 pub const fn empty() -> Self {
134 Self { bits: 0 }
135 }
136
137 #[must_use]
140 pub const fn all() -> Self {
141 Self {
142 bits: Self::ALL_BITS,
143 }
144 }
145
146 #[must_use]
148 pub const fn graceful() -> Self {
149 Self::empty()
150 .with(Signal::Terminate)
151 .with(Signal::Interrupt)
152 .with(Signal::Hangup)
153 }
154
155 #[must_use]
157 pub const fn standard() -> Self {
158 Self::graceful().with(Signal::Quit)
159 }
160
161 #[must_use]
163 pub const fn with(self, sig: Signal) -> Self {
164 Self {
165 bits: self.bits | sig.bit(),
166 }
167 }
168
169 #[must_use]
171 pub const fn without(self, sig: Signal) -> Self {
172 Self {
173 bits: self.bits & !sig.bit(),
174 }
175 }
176
177 #[must_use]
179 pub const fn contains(self, sig: Signal) -> bool {
180 (self.bits & sig.bit()) != 0
181 }
182
183 #[must_use]
185 pub const fn is_empty(self) -> bool {
186 self.bits == 0
187 }
188
189 #[must_use]
191 pub const fn len(self) -> usize {
192 self.bits.count_ones() as usize
193 }
194
195 #[must_use]
197 pub const fn iter(&self) -> SignalSetIter {
198 SignalSetIter {
199 set: *self,
200 index: 0,
201 }
202 }
203}
204
205impl Default for SignalSet {
206 fn default() -> Self {
207 Self::graceful()
208 }
209}
210
211impl IntoIterator for SignalSet {
212 type Item = Signal;
213 type IntoIter = SignalSetIter;
214 fn into_iter(self) -> Self::IntoIter {
215 self.iter()
216 }
217}
218
219#[derive(Debug, Clone)]
221pub struct SignalSetIter {
222 set: SignalSet,
223 index: usize,
224}
225
226impl Iterator for SignalSetIter {
227 type Item = Signal;
228
229 fn next(&mut self) -> Option<Signal> {
230 while self.index < Signal::ALL.len() {
231 let sig = Signal::ALL[self.index];
232 self.index += 1;
233 if self.set.contains(sig) {
234 return Some(sig);
235 }
236 }
237 None
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[test]
246 fn signal_display_matches_description() {
247 for s in Signal::ALL {
248 assert_eq!(format!("{s}"), s.description());
249 }
250 }
251
252 #[test]
253 fn signal_unix_number_round_trip() {
254 assert_eq!(Signal::Terminate.unix_number(), Some(15));
255 assert_eq!(Signal::Interrupt.unix_number(), Some(2));
256 assert_eq!(Signal::Hangup.unix_number(), Some(1));
257 assert_eq!(Signal::Quit.unix_number(), Some(3));
258 assert_eq!(Signal::Pipe.unix_number(), Some(13));
259 assert_eq!(Signal::User1.unix_number(), Some(10));
260 assert_eq!(Signal::User2.unix_number(), Some(12));
261 }
262
263 #[test]
264 fn is_unix_only_is_correct() {
265 assert!(Signal::Pipe.is_unix_only());
266 assert!(Signal::User1.is_unix_only());
267 assert!(Signal::User2.is_unix_only());
268 assert!(!Signal::Terminate.is_unix_only());
269 assert!(!Signal::Interrupt.is_unix_only());
270 assert!(!Signal::Quit.is_unix_only());
271 assert!(!Signal::Hangup.is_unix_only());
272 }
273
274 #[test]
275 fn set_empty_and_all() {
276 assert!(SignalSet::empty().is_empty());
277 assert_eq!(SignalSet::empty().len(), 0);
278 assert_eq!(SignalSet::all().len(), 7);
279 }
280
281 #[test]
282 fn set_graceful_contents() {
283 let g = SignalSet::graceful();
284 assert!(g.contains(Signal::Terminate));
285 assert!(g.contains(Signal::Interrupt));
286 assert!(g.contains(Signal::Hangup));
287 assert!(!g.contains(Signal::Quit));
288 assert!(!g.contains(Signal::Pipe));
289 assert_eq!(g.len(), 3);
290 }
291
292 #[test]
293 fn set_standard_contents() {
294 let s = SignalSet::standard();
295 assert!(s.contains(Signal::Quit));
296 assert_eq!(s.len(), 4);
297 }
298
299 #[test]
300 fn with_without_idempotent() {
301 let s = SignalSet::empty()
302 .with(Signal::Terminate)
303 .with(Signal::Terminate);
304 assert_eq!(s.len(), 1);
305 let s = s.without(Signal::Terminate).without(Signal::Terminate);
306 assert!(s.is_empty());
307 }
308
309 #[test]
310 fn iter_canonical_order() {
311 let s = SignalSet::all();
312 let v: Vec<Signal> = s.iter().collect();
313 assert_eq!(v, Signal::ALL.to_vec());
314 }
315
316 #[test]
317 fn default_is_graceful() {
318 assert_eq!(SignalSet::default(), SignalSet::graceful());
319 }
320}