forge
ExploreLog in

Tahinli/forge

public
0 watching
★ 0
3.1 KiB
1//! The subuid unpack helper. [`unpack_helper_main`] is the body of `forge-runner __sandbox-unpack`.
2//! It runs inside the mapped user namespace, so files it writes get the subordinate owners from the
3//! layers. [`run_unpack_helper`] is the runner side that starts it.
4
5use std::io::{self, Read, Write};
6use std::path::{Path, PathBuf};
7use std::process::{Command, Stdio};
8
9use serde::{Deserialize, Serialize};
10
11use super::unpack::{Ownership, UnpackError, UnpackStats, unpack_layers};
12use crate::sandbox::idmap::{IdMap, enter_mapped_userns};
13
14/// What the runner writes to the helper's stdin, as JSON.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub(super) struct UnpackRequest {
17 pub map: IdMap,
18 pub rootfs: PathBuf,
19 pub layers: Vec<PathBuf>,
20 pub max_bytes: u64,
21}
22
23/// Runs `<exe> __sandbox-unpack` with `req` on stdin and returns the stats it prints on stdout.
24pub(super) fn run_unpack_helper(
25 exe: &Path,
26 req: &UnpackRequest,
27) -> Result<UnpackStats, UnpackError> {
28 let body = serde_json::to_vec(req).map_err(|err| UnpackError::Helper(err.to_string()))?;
29 let mut child = Command::new(exe)
30 .arg("__sandbox-unpack")
31 .stdin(Stdio::piped())
32 .stdout(Stdio::piped())
33 .stderr(Stdio::piped())
34 .spawn()?;
35 if let Some(mut stdin) = child.stdin.take() {
36 // A helper that exits early says why on stderr, which is reported below, so a failed
37 // write is not an error of its own here.
38 let _ = stdin.write_all(&body);
39 }
40 let out = child.wait_with_output()?;
41 if !out.status.success() {
42 let stderr = String::from_utf8_lossy(&out.stderr);
43 return Err(UnpackError::Helper(format!(
44 "{} ({})",
45 stderr.trim(),
46 out.status
47 )));
48 }
49 serde_json::from_slice(&out.stdout)
50 .map_err(|err| UnpackError::Helper(format!("bad stats from the helper: {err}")))
51}
52
53/// Entry point for `forge-runner __sandbox-unpack`. Returns the process exit code: 0 after the
54/// stats are printed, or 125 with a `forge-sandbox:` line on stderr.
55///
56/// Must run before any thread is started, because it enters a user namespace.
57pub fn unpack_helper_main() -> i32 {
58 match run_helper() {
59 Ok(()) => 0,
60 Err(message) => {
61 eprintln!("forge-sandbox: {message}");
62 125
63 }
64 }
65}
66
67fn run_helper() -> Result<(), String> {
68 let mut input = Vec::new();
69 io::stdin()
70 .read_to_end(&mut input)
71 .map_err(|err| format!("cannot read the request: {err}"))?;
72 let req: UnpackRequest =
73 serde_json::from_slice(&input).map_err(|err| format!("bad request: {err}"))?;
74 enter_mapped_userns(&req.map).map_err(|err| err.to_string())?;
75 let ownership = Ownership::Preserve {
76 size: req.map.size(),
77 };
78 let stats = unpack_layers(&req.rootfs, &req.layers, req.max_bytes, ownership)
79 .map_err(|err| err.to_string())?;
80 let json = serde_json::to_vec(&stats).map_err(|err| err.to_string())?;
81 let mut stdout = io::stdout().lock();
82 stdout
83 .write_all(&json)
84 .and_then(|()| stdout.flush())
85 .map_err(|err| format!("cannot write the stats: {err}"))
86}