#![cfg(target_os = "linux")]
use std::ffi::OsString;
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 CANARY_EXIT_CODE: i32 = 42;
const COMMAND_COMPLETED_TYPE: &str = "dev.cellos.events.cell.command.v1.completed";
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);
}
let crate_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let workspace = crate_dir
.parent()
.and_then(|p| p.parent())
.expect("cellos-host-firecracker sits two levels under workspace root");
for profile in ["release", "debug"] {
let candidate = workspace
.join("target")
.join(profile)
.join("cellos-supervisor");
if candidate.is_file() {
return candidate;
}
}
workspace
.join("target")
.join("release")
.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!("firecracker_e2e: skipping FC-16 — {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(current) = walker.pop() {
let entries = match fs::read_dir(¤t) {
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 find_exit_code_in_jsonl(path: &Path) -> Result<Option<i64>, String> {
let contents = fs::read_to_string(path).map_err(|e| format!("read {}: {e}", path.display()))?;
for (line_no, line) in contents.lines().enumerate() {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
let value: serde_json::Value = serde_json::from_str(trimmed).map_err(|e| {
format!(
"{}:{}: malformed JSONL line: {e}",
path.display(),
line_no + 1
)
})?;
let event_type = value.get("type").and_then(|v| v.as_str()).unwrap_or("");
if event_type != COMMAND_COMPLETED_TYPE {
continue;
}
let exit_code = value
.get("data")
.and_then(|d| d.get("exitCode"))
.and_then(|c| c.as_i64())
.ok_or_else(|| {
format!(
"{}:{}: `{COMMAND_COMPLETED_TYPE}` event has no integer `data.exitCode`; \
full event: {trimmed}",
path.display(),
line_no + 1,
)
})?;
return Ok(Some(exit_code));
}
Ok(None)
}
#[test]
fn fc16_exit_code_canary_42() {
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() && 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` \
(or set CELLOS_SUPERVISOR_BIN to a built binary)",
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-e2e-exit42",
"authority": { "secretRefs": [], "egressRules": [] },
"lifetime": { "ttlSeconds": 30 },
"run": {
"argv": ["/bin/sh", "-c", "exit 42"],
"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!(
"fc16_exit_code_canary_42: spawning supervisor {} with /bin/sh -c \"exit 42\"",
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 jsonl_files = collect_jsonl_files(&export_dir);
if jsonl_files.is_empty() {
panic!(
"no JSONL event files emitted under {}\n--- stderr ---\n{stderr_buf}\n\
--- stdout ---\n{stdout_buf}",
export_dir.display()
);
}
let mut found_exit_code: Option<i64> = None;
let mut scan_errors: Vec<String> = Vec::new();
for file in &jsonl_files {
match find_exit_code_in_jsonl(file) {
Ok(Some(code)) => {
found_exit_code = Some(code);
break;
}
Ok(None) => continue,
Err(e) => scan_errors.push(e),
}
}
let recorded = found_exit_code.unwrap_or_else(|| {
let scanned: Vec<String> = jsonl_files
.iter()
.map(|p| p.display().to_string())
.collect();
panic!(
"FC-16: no `{COMMAND_COMPLETED_TYPE}` event found in any JSONL file under {}. \
The supervisor produced JSONL output but it did not contain a command-completed \
event — this means the cell never reported a clean exit through the vsock \
handshake (FC-19), or the supervisor failed to emit the event after receiving it.\n\
Scanned files: {scanned:?}\n\
Scan errors: {scan_errors:?}\n\
--- stderr ---\n{stderr_buf}\n--- stdout ---\n{stdout_buf}",
export_dir.display(),
)
});
assert_eq!(
recorded, CANARY_EXIT_CODE as i64,
"FC-16 violation: supervisor recorded exit code {recorded}, expected {CANARY_EXIT_CODE}.\n\
The cell ran `/bin/sh -c \"exit 42\"`, so the only authentic value is 42. \
Common regressions:\n\
* recorded=0: exit code is being defaulted (the field is unwired or the cell \
succeeded for the wrong reason — `cellos-init` fell through to \
power-off without reading the workload's status).\n\
* recorded=1: a generic `non-zero` is being substituted for the actual code \
somewhere between `cellos-init` and the supervisor's event \
recorder.\n\
* recorded=128+N: the workload was signal-killed and the i32 carries the kernel's \
waitpid(2) signal-encoding rather than the script's `exit` value.\n\
* recorded=other: vsock byte-ordering mismatch or buffer aliasing — the value is \
traveling but corrupted en route.\n\
--- stderr ---\n{stderr_buf}\n--- stdout ---\n{stdout_buf}"
);
drop(tmp);
let _ = OsString::new(); }