Skip to main content

alien_core/
sandbox_signal.rs

1//! The signals a caller may send to an interactive sandbox command.
2//!
3//! A closed set rather than a number: signal numbers differ between kernels and architectures,
4//! and a caller that could name any number could also name one that stops the agent's own
5//! supervision (`SIGSTOP`) rather than the command. Every name here means the same thing on every
6//! backend, and each backend maps it at the last moment.
7
8use serde::{Deserialize, Serialize};
9
10/// A signal for an interactive command. It reaches the command's whole process group.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
13#[serde(rename_all = "camelCase")]
14pub enum CommandSignal {
15    /// `SIGINT`, what Ctrl-C sends: a REPL abandons the current input and keeps running
16    Interrupt,
17    /// `SIGTERM`, a request to exit
18    Terminate,
19    /// `SIGKILL`, which cannot be caught
20    Kill,
21    /// `SIGHUP`
22    Hangup,
23    /// `SIGQUIT`
24    Quit,
25    /// `SIGUSR1`
26    User1,
27    /// `SIGUSR2`
28    User2,
29}
30
31impl CommandSignal {
32    /// The name `kill -s` takes, for backends that signal through a shell inside the sandbox.
33    pub fn posix_name(self) -> &'static str {
34        match self {
35            Self::Interrupt => "INT",
36            Self::Terminate => "TERM",
37            Self::Kill => "KILL",
38            Self::Hangup => "HUP",
39            Self::Quit => "QUIT",
40            Self::User1 => "USR1",
41            Self::User2 => "USR2",
42        }
43    }
44
45    /// Every signal, for callers that validate a name or test each one.
46    pub const ALL: [Self; 7] = [
47        Self::Interrupt,
48        Self::Terminate,
49        Self::Kill,
50        Self::Hangup,
51        Self::Quit,
52        Self::User1,
53        Self::User2,
54    ];
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    /// The wire names are the contract with every binding and agent; a rename here breaks an agent
62    /// already baked into an image.
63    #[test]
64    fn signals_serialize_as_their_wire_names() {
65        let names: Vec<String> = CommandSignal::ALL
66            .iter()
67            .map(|signal| {
68                serde_json::to_value(signal)
69                    .unwrap()
70                    .as_str()
71                    .unwrap()
72                    .to_string()
73            })
74            .collect();
75        assert_eq!(
76            names,
77            [
78                "interrupt",
79                "terminate",
80                "kill",
81                "hangup",
82                "quit",
83                "user1",
84                "user2"
85            ]
86        );
87    }
88
89    #[test]
90    fn an_unknown_signal_is_refused() {
91        assert!(serde_json::from_str::<CommandSignal>("\"stop\"").is_err());
92    }
93}