runtime/tools/exec/container/
guardian.rs1use std::io::{Read, Write};
9use std::path::Path;
10use std::process::{Child, Command, Stdio};
11use std::time::Duration;
12
13use kernel::AxError;
14
15use super::{
16 cleanup::{Cleanup, Confirmation},
17 control, denied,
18};
19use serde::{Deserialize, Serialize};
20
21const ARGUMENT: &str = "__container-guardian";
22const READY: &[u8] = b"ready\n";
23const CREATE: &[u8] = b"create\n";
24const DONE: &[u8] = b"done\n";
25const POLLS: usize = 200;
26const INTERVAL: Duration = Duration::from_millis(10);
27const RETRY: Duration = Duration::from_secs(5);
28
29pub(super) struct Launch<'a> {
30 pub(super) cleanup: &'a Cleanup,
31 pub(super) command: &'a Command,
32}
33
34#[derive(Serialize, Deserialize)]
35struct Ownership {
36 #[serde(flatten)]
37 cleanup: Cleanup,
38 arguments: Vec<std::ffi::OsString>,
39 answer: std::path::PathBuf,
40}
41
42pub(super) struct Guard {
43 child: Child,
44 answer: std::path::PathBuf,
45}
46
47impl Guard {
48 pub(super) fn spawn(
49 harness: &Path,
50 launch: Launch<'_>,
51 ready: &Path,
52 errors: std::fs::File,
53 ) -> Result<Self, AxError> {
54 let answer = ready.with_extension("created");
55 std::fs::File::create(&answer)
56 .map_err(|err| denied("prepare container creation response", err))?;
57 let record = serde_json::to_vec(&Ownership {
58 cleanup: launch.cleanup.clone(),
59 arguments: launch
60 .command
61 .get_args()
62 .map(std::ffi::OsStr::to_os_string)
63 .collect(),
64 answer: answer.clone(),
65 })
66 .map_err(|err| denied("encode container guardian ownership", err))?;
67 let ownership = ready.with_extension("ownership");
68 std::fs::write(&ownership, record)
69 .map_err(|err| denied("keep container guardian ownership", err))?;
70 let output = std::fs::File::create(ready)
71 .map_err(|err| denied("prepare container guardian readiness", err))?;
72 let child = Command::new(harness)
73 .arg(ARGUMENT)
74 .arg(ownership)
75 .stdin(Stdio::piped())
76 .stdout(output)
77 .stderr(errors)
78 .spawn()
79 .map_err(|err| denied("start the container guardian", err))?;
80 let mut guard = Self { child, answer };
81 let ready_budget = u64::try_from(READY.len().saturating_add(1))
82 .map_err(|err| denied("read container guardian readiness", err))?;
83 for _ in 0..POLLS {
84 let mut bytes = Vec::with_capacity(READY.len().saturating_add(1));
85 std::fs::File::open(ready)
86 .and_then(|file| file.take(ready_budget).read_to_end(&mut bytes))
87 .map_err(|err| denied("read container guardian readiness", err))?;
88 if bytes == READY {
89 return Ok(guard);
90 }
91 if let Some(status) = guard
92 .child
93 .try_wait()
94 .map_err(|err| denied("poll container guardian readiness", err))?
95 {
96 return Err(denied(
97 "start the container guardian",
98 format!("helper ended before ready: {status}"),
99 ));
100 }
101 std::thread::sleep(INTERVAL);
102 }
103 Err(denied(
104 "start the container guardian",
105 "helper did not confirm readiness within its poll budget",
106 ))
107 }
108
109 pub(super) fn create(&mut self) -> Result<(), AxError> {
110 self.child
111 .stdin
112 .as_mut()
113 .ok_or_else(|| denied("request container creation", "guardian channel closed"))?
114 .write_all(CREATE)
115 .map_err(|err| denied("request container creation", err))?;
116 for _ in 0..control::POLLS {
117 let bytes = std::fs::read(&self.answer)
118 .map_err(|err| denied("read container creation response", err))?;
119 if bytes.last() == Some(&b'\n') {
120 return serde_json::from_slice::<Result<(), AxError>>(&bytes)
121 .map_err(|err| denied("decode container creation response", err))?;
122 }
123 std::thread::sleep(control::INTERVAL);
124 }
125 Err(denied(
126 "wait for container creation",
127 "guardian retains the in-flight request",
128 ))
129 }
130
131 pub(super) fn finish_cleanup(&mut self) -> Result<(), AxError> {
132 drop(self.child.stdin.take());
133 match self
134 .child
135 .try_wait()
136 .map_err(|err| denied("reap the container guardian", err))?
137 {
138 Some(status) if status.success() => Ok(()),
139 Some(status) => Err(denied("reap the container guardian", status)),
140 None => Err(denied(
141 "wait for guardian cleanup",
142 "guardian still owns creation and removal",
143 )),
144 }
145 }
146
147 pub(super) fn disarm(&mut self) -> Result<(), AxError> {
148 if let Some(status) = self
149 .child
150 .try_wait()
151 .map_err(|err| denied("reap the container guardian", err))?
152 {
153 return if status.success() {
154 Ok(())
155 } else {
156 Err(denied("reap the container guardian", status))
157 };
158 }
159 if let Some(mut input) = self.child.stdin.take() {
160 input
161 .write_all(DONE)
162 .map_err(|err| denied("release the container guardian", err))?;
163 }
164 for _ in 0..POLLS {
165 if let Some(status) = self
166 .child
167 .try_wait()
168 .map_err(|err| denied("reap the container guardian", err))?
169 {
170 return if status.success() {
171 Ok(())
172 } else {
173 Err(denied("reap the container guardian", status))
174 };
175 }
176 std::thread::sleep(INTERVAL);
177 }
178 Err(denied(
179 "reap the container guardian",
180 "helper remains responsible after the poll budget",
181 ))
182 }
183}
184
185pub fn run_container_guard(args: &[String]) -> Option<Result<(), AxError>> {
193 if args.first().map(String::as_str) != Some(ARGUMENT) {
194 return None;
195 }
196 Some(run(args))
197}
198
199fn run(args: &[String]) -> Result<(), AxError> {
200 let [_, record] = args else {
201 return Err(denied(
202 "read container guardian ownership",
203 "expected one cleanup record",
204 ));
205 };
206 let bytes =
207 std::fs::read(record).map_err(|err| denied("read container guardian ownership", err))?;
208 let ownership: Ownership = serde_json::from_slice(&bytes)
209 .map_err(|err| denied("decode container guardian ownership", err))?;
210 std::io::stdout()
211 .lock()
212 .write_all(READY)
213 .map_err(|err| denied("confirm container guardian readiness", err))?;
214 let mut input = std::io::stdin().lock();
215 let mut confirmation = Confirmation::Absence;
216 let request = read_command(&mut input);
217 match request {
218 Ok(answer) if answer == CREATE => {
219 let mut create = Command::new(&ownership.cleanup.program);
220 create.args(&ownership.arguments);
221 let created =
222 control::output(&mut create, control::Wait::Creation).and_then(|answer| {
223 if answer.status.success() {
224 Ok(())
225 } else {
226 Err(denied(
227 "create the owned container",
228 String::from_utf8_lossy(&answer.stderr),
229 ))
230 }
231 });
232 if created.is_err() {
233 confirmation = Confirmation::Deletion;
234 }
235 let published = serde_json::to_vec(&created)
236 .map_err(|err| denied("encode container creation response", err))
237 .and_then(|mut bytes| {
238 bytes.push(b'\n');
239 std::fs::write(&ownership.answer, bytes)
240 .map_err(|err| denied("publish container creation response", err))
241 });
242 if let Err(err) = published {
243 eprintln!("{err}; guardian takes cleanup responsibility");
244 } else {
245 match read_command(&mut input) {
246 Ok(answer) if answer == DONE && created.is_ok() => return Ok(()),
247 Ok(_) => {}
248 Err(err) => eprintln!("{err}; guardian takes cleanup responsibility"),
249 }
250 }
251 }
252 Ok(answer) if answer == DONE => return Ok(()),
253 Ok(_) => {}
254 Err(err) => eprintln!("{err}; guardian takes cleanup responsibility"),
255 }
256 loop {
257 match ownership.cleanup.remove(confirmation).and_then(|()| {
258 confirmation = Confirmation::Absence;
259 ownership.cleanup.release_copy()
260 }) {
261 Ok(()) => return Ok(()),
262 Err(err) => {
263 eprintln!(
264 "{err}; container {} remains owned by its guardian",
265 ownership.cleanup.name
266 );
267 std::thread::sleep(RETRY);
268 }
269 }
270 }
271}
272
273fn read_command(input: &mut impl Read) -> Result<Vec<u8>, AxError> {
274 let mut answer = Vec::with_capacity(CREATE.len());
275 for _ in 0..CREATE.len() {
276 let mut byte = [0_u8; 1];
277 let count = input
278 .read(&mut byte)
279 .map_err(|err| denied("read guardian command", err))?;
280 if count == 0 {
281 break;
282 }
283 answer.extend_from_slice(&byte);
284 if byte == *b"\n" {
285 break;
286 }
287 }
288 Ok(answer)
289}