//! The subuid unpack helper. [`unpack_helper_main`] is the body of `forge-runner __sandbox-unpack`. //! It runs inside the mapped user namespace, so files it writes get the subordinate owners from the //! layers. [`run_unpack_helper`] is the runner side that starts it. use std::io::{self, Read, Write}; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use serde::{Deserialize, Serialize}; use super::unpack::{Ownership, UnpackError, UnpackStats, unpack_layers}; use crate::sandbox::idmap::{IdMap, enter_mapped_userns}; /// What the runner writes to the helper's stdin, as JSON. #[derive(Debug, Clone, Serialize, Deserialize)] pub(super) struct UnpackRequest { pub map: IdMap, pub rootfs: PathBuf, pub layers: Vec, pub max_bytes: u64, } /// Runs ` __sandbox-unpack` with `req` on stdin and returns the stats it prints on stdout. pub(super) fn run_unpack_helper( exe: &Path, req: &UnpackRequest, ) -> Result { let body = serde_json::to_vec(req).map_err(|err| UnpackError::Helper(err.to_string()))?; let mut child = Command::new(exe) .arg("__sandbox-unpack") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn()?; if let Some(mut stdin) = child.stdin.take() { // A helper that exits early says why on stderr, which is reported below, so a failed // write is not an error of its own here. let _ = stdin.write_all(&body); } let out = child.wait_with_output()?; if !out.status.success() { let stderr = String::from_utf8_lossy(&out.stderr); return Err(UnpackError::Helper(format!( "{} ({})", stderr.trim(), out.status ))); } serde_json::from_slice(&out.stdout) .map_err(|err| UnpackError::Helper(format!("bad stats from the helper: {err}"))) } /// Entry point for `forge-runner __sandbox-unpack`. Returns the process exit code: 0 after the /// stats are printed, or 125 with a `forge-sandbox:` line on stderr. /// /// Must run before any thread is started, because it enters a user namespace. pub fn unpack_helper_main() -> i32 { match run_helper() { Ok(()) => 0, Err(message) => { eprintln!("forge-sandbox: {message}"); 125 } } } fn run_helper() -> Result<(), String> { let mut input = Vec::new(); io::stdin() .read_to_end(&mut input) .map_err(|err| format!("cannot read the request: {err}"))?; let req: UnpackRequest = serde_json::from_slice(&input).map_err(|err| format!("bad request: {err}"))?; enter_mapped_userns(&req.map).map_err(|err| err.to_string())?; let ownership = Ownership::Preserve { size: req.map.size(), }; let stats = unpack_layers(&req.rootfs, &req.layers, req.max_bytes, ownership) .map_err(|err| err.to_string())?; let json = serde_json::to_vec(&stats).map_err(|err| err.to_string())?; let mut stdout = io::stdout().lock(); stdout .write_all(&json) .and_then(|()| stdout.flush()) .map_err(|err| format!("cannot write the stats: {err}")) }