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}