#![cfg(target_os = "linux")]
use std::fs::{self, File};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::time::{Duration, Instant};
const REQUIRED_ENV: &[&str] = &[
"CELLOS_FIRECRACKER_BINARY",
"CELLOS_FIRECRACKER_KERNEL_IMAGE",
"CELLOS_FIRECRACKER_ROOTFS_IMAGE",
"CELLOS_FIRECRACKER_SOCKET_DIR",
];
fn supervisor_exe() -> PathBuf {
if let Some(p) = std::env::var_os("CELLOS_SUPERVISOR_BIN") {
return PathBuf::from(p);
}
for key in [
"CARGO_BIN_EXE_cellos-supervisor",
"CARGO_BIN_EXE_cellos_supervisor",
] {
if let Some(p) = std::env::var_os(key) {
return PathBuf::from(p);
}
}
let crate_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let workspace = crate_dir
.parent()
.and_then(|p| p.parent())
.expect("cellos-supervisor sits two levels under workspace root");
let profile = std::env::var("PROFILE").unwrap_or_else(|_| "release".into());
workspace
.join("target")
.join(profile)
.join("cellos-supervisor")
}
fn handle_rootfs_alias() {
let long = std::env::var_os("CELLOS_FIRECRACKER_ROOTFS_IMAGE");
let short = std::env::var_os("CELLOS_FIRECRACKER_ROOTFS");
match (long, short) {
(Some(_), _) => {}
(None, Some(s)) => std::env::set_var("CELLOS_FIRECRACKER_ROOTFS_IMAGE", s),
_ => {}
}
}
fn skip(reason: &str) {
eprintln!("fc12_pid1_assertion: skipping — {reason}");
}
fn collect_jsonl_files(dir: &Path) -> Vec<PathBuf> {
let mut out = Vec::new();
let mut walker = vec![dir.to_path_buf()];
while let Some(d) = walker.pop() {
let entries = match fs::read_dir(&d) {
Ok(it) => it,
Err(_) => continue,
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
walker.push(path);
} else if path.extension().and_then(|s| s.to_str()) == Some("jsonl") {
out.push(path);
}
}
}
out
}
fn read_command_exit_code(export_dir: &Path) -> Result<i32, String> {
const TYPE_NEEDLE: &str = "dev.cellos.events.cell.command.v1.completed";
for path in collect_jsonl_files(export_dir) {
let content = match fs::read_to_string(&path) {
Ok(c) => c,
Err(e) => {
eprintln!("fc12: skipping unreadable jsonl {}: {e}", path.display());
continue;
}
};
for line in content.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
let value: serde_json::Value = match serde_json::from_str(line) {
Ok(v) => v,
Err(e) => {
eprintln!(
"fc12: skipping malformed jsonl line in {}: {e}",
path.display()
);
continue;
}
};
let ty = value.get("type").and_then(|v| v.as_str()).unwrap_or("");
if ty != TYPE_NEEDLE {
continue;
}
let exit_code = value
.get("data")
.and_then(|d| d.get("exitCode"))
.and_then(|v| v.as_i64())
.ok_or_else(|| {
format!(
"found {TYPE_NEEDLE} event in {} but data.exitCode missing or not i64; \
payload: {line}",
path.display()
)
})?;
return Ok(exit_code as i32);
}
}
Err(format!(
"no {TYPE_NEEDLE} event found under {}; either the supervisor never \
spawned the workload (precondition failure) or the export sink did not \
flush before the supervisor exited",
export_dir.display()
))
}
#[test]
fn cellos_init_runs_as_pid1_in_firecracker_microvm() {
if !Path::new("/dev/kvm").exists() {
skip("/dev/kvm not present (no KVM on this host)");
return;
}
handle_rootfs_alias();
let missing: Vec<&str> = REQUIRED_ENV
.iter()
.copied()
.filter(|k| std::env::var_os(k).is_none())
.collect();
if !missing.is_empty() {
skip(&format!("missing env: {}", missing.join(", ")));
return;
}
for key in [
"CELLOS_FIRECRACKER_BINARY",
"CELLOS_FIRECRACKER_KERNEL_IMAGE",
"CELLOS_FIRECRACKER_ROOTFS_IMAGE",
] {
let path = std::env::var(key).expect("checked above");
if !Path::new(&path).exists() {
skip(&format!("{key}={path} does not exist on disk"));
return;
}
}
let sock_dir = std::env::var("CELLOS_FIRECRACKER_SOCKET_DIR").expect("checked");
if !Path::new(&sock_dir).is_dir() {
if fs::create_dir_all(&sock_dir).is_err() {
skip(&format!("socket dir {sock_dir} not creatable"));
return;
}
}
let exe = supervisor_exe();
if !exe.is_file() {
skip(&format!(
"supervisor binary missing at {} — run \
`cargo build -p cellos-supervisor --release`",
exe.display()
));
return;
}
let tmp = tempfile::tempdir().expect("tempdir");
let spec_path = tmp.path().join("cell.json");
let spec_json = r#"{
"apiVersion": "cellos.io/v1",
"kind": "ExecutionCell",
"spec": {
"id": "fc-12-pid1-assertion",
"authority": { "secretRefs": [], "egressRules": [] },
"lifetime": { "ttlSeconds": 30 },
"run": {
"secretDelivery": "env",
"argv": ["/bin/sh", "-c", "test \"$(cat /proc/1/comm)\" = cellos-init"],
"timeoutMs": 20000,
"limits": { "memoryMaxBytes": 67108864 }
}
}
}"#;
File::create(&spec_path)
.and_then(|mut f| f.write_all(spec_json.as_bytes()))
.expect("write cell spec");
let export_dir = tmp.path().join("events");
fs::create_dir_all(&export_dir).expect("mkdir export dir");
let mut cmd = Command::new(&exe);
cmd.env("CELL_OS_USE_NOOP_SINK", "1") .env("CELLOS_CELL_BACKEND", "firecracker")
.env("CELLOS_EXPORT_DIR", &export_dir)
.env("RUST_BACKTRACE", "1")
.arg(&spec_path)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
for (k, v) in std::env::vars_os() {
if k.to_string_lossy().starts_with("CELLOS_FIRECRACKER_") {
cmd.env(&k, &v);
}
}
eprintln!("fc12_pid1_assertion: spawning supervisor {}", exe.display());
let mut child = cmd.spawn().expect("spawn supervisor");
let deadline = Instant::now() + Duration::from_secs(30);
let status = loop {
match child.try_wait().expect("try_wait") {
Some(status) => break status,
None if Instant::now() >= deadline => {
let _ = child.kill();
let _ = child.wait();
panic!("supervisor did not exit within 30s");
}
None => std::thread::sleep(Duration::from_millis(200)),
}
};
let mut stderr_buf = String::new();
let mut stdout_buf = String::new();
if let Some(mut s) = child.stderr.take() {
use std::io::Read;
let _ = s.read_to_string(&mut stderr_buf);
}
if let Some(mut s) = child.stdout.take() {
use std::io::Read;
let _ = s.read_to_string(&mut stdout_buf);
}
let exit_code = read_command_exit_code(&export_dir).unwrap_or_else(|e| {
panic!(
"FC-12 evidence missing: {e}\n\
supervisor process status: {status:?}\n\
--- stderr ---\n{stderr_buf}\n--- stdout ---\n{stdout_buf}"
)
});
assert_eq!(
exit_code, 0,
"FC-12 violation: workload exited with code {exit_code} (expected 0). \
The workload is `test \"$(cat /proc/1/comm)\" = cellos-init`; a non-zero \
exit means PID 1's `comm` field was something other than `cellos-init`. \
Re-check the Firecracker boot path's `/sbin/init` symlink and the \
cellos-init binary's argv[0].\n\
supervisor process status: {status:?}\n\
--- stderr ---\n{stderr_buf}\n--- stdout ---\n{stdout_buf}"
);
drop(tmp);
}