mod crash_capture;
use std::path::Path;
use std::thread;
use std::time::{Duration, Instant};
use boxlite::{
util,
vmm::{self, ExitInfo, InstanceSpec, VmmConfig, controller::watchdog},
};
use boxlite_shared::errors::{BoxliteError, BoxliteResult};
use crash_capture::CrashCapture;
#[allow(unused_imports)]
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
#[cfg(feature = "gvproxy")]
use boxlite::net::gvproxy::GvproxyInstance;
fn init_logging(box_dir: &Path) -> tracing_appender::non_blocking::WorkerGuard {
let logs_dir = box_dir.join("logs");
std::fs::create_dir_all(&logs_dir).expect("Failed to create logs directory");
let file_appender = tracing_appender::rolling::daily(logs_dir, "boxlite-shim.log");
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
let env_filter = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap();
util::register_to_tracing(non_blocking, env_filter);
guard
}
fn main() -> BoxliteResult<()> {
let t0 = Instant::now();
let timing = |msg: &str| eprintln!("[shim] T+{}ms: {msg}", t0.elapsed().as_millis());
let wall = chrono::Utc::now().format("%H:%M:%S%.6f");
eprintln!("[shim] {wall} T+0ms: main() entered");
let config_json = {
use std::io::Read;
let mut buf = String::new();
std::io::stdin()
.read_to_string(&mut buf)
.map_err(|e| BoxliteError::Engine(format!("Failed to read config from stdin: {e}")))?;
buf
};
#[allow(unused_mut)]
let mut config: InstanceSpec = serde_json::from_str(&config_json)
.map_err(|e| BoxliteError::Engine(format!("Failed to parse config JSON: {e}")))?;
timing("config parsed");
let box_dir = config
.exit_file
.parent()
.unwrap_or(Path::new("."))
.to_path_buf();
let _log_guard = init_logging(&box_dir);
timing("logging initialized");
CrashCapture::install(config.exit_file.clone());
tracing::info!(
engine = ?config.engine,
box_id = %config.box_id,
"Box runner starting"
);
let exit_file = config.exit_file.clone();
run_shim(config, timing).inspect_err(|e| {
let info = ExitInfo::Error {
exit_code: 1,
message: e.to_string(),
};
if let Ok(json) = serde_json::to_string(&info) {
let _ = std::fs::write(&exit_file, json);
}
})
}
#[allow(unused_mut)]
fn run_shim(mut config: InstanceSpec, timing: impl Fn(&str)) -> BoxliteResult<()> {
tracing::debug!(
shares = ?config.fs_shares.shares(),
"Filesystem shares configured"
);
tracing::debug!(
entrypoint = ?config.guest_entrypoint.executable,
"Guest entrypoint configured"
);
#[cfg(feature = "gvproxy")]
if let Some(ref net_config) = config.network_config {
let (gvproxy, endpoint) = GvproxyInstance::from_config(net_config)?;
config.network_backend_endpoint = Some(endpoint);
timing("gvproxy created");
Box::leak(Box::new(gvproxy));
}
#[cfg(target_os = "linux")]
{
use boxlite::jailer::seccomp;
if config.security.jailer_enabled && config.security.seccomp_enabled {
tracing::info!(
box_id = %config.box_id,
"Applying VMM seccomp filter (TSYNC)"
);
seccomp::apply_vmm_filter(&config.box_id)?;
tracing::info!(
box_id = %config.box_id,
"Seccomp isolation complete"
);
} else if config.security.jailer_enabled {
tracing::warn!(
box_id = %config.box_id,
"Seccomp disabled - running without syscall filtering"
);
} else {
tracing::warn!(
box_id = %config.box_id,
"Jailer disabled - running without process isolation"
);
}
}
let detach = config.detach;
let transport = config.transport.clone();
let options = VmmConfig::default();
let mut engine = vmm::create_engine(config.engine, options)?;
timing("engine created");
tracing::info!("Engine created, creating Box instance");
let instance = match engine.create(config) {
Ok(instance) => instance,
Err(e) => {
tracing::error!("Failed to create Box instance: {}", e);
return Err(e);
}
};
timing("instance created (krun FFI calls done)");
tracing::info!("Box instance created, handing over process control to Box");
install_graceful_shutdown_handler(transport);
if !detach {
start_parent_watchdog();
tracing::info!("Parent watchdog started via pipe POLLHUP (detach=false)");
} else {
tracing::info!("Running in detached mode (detach=true)");
}
timing("entering VM (krun_start_enter)");
match instance.enter() {
Ok(()) => {
tracing::info!("Box execution completed successfully");
Ok(())
}
Err(e) => {
tracing::error!("Box execution failed: {}", e);
Err(e)
}
}
}
const GRACEFUL_SHUTDOWN_TIMEOUT_SECS: u64 = 5;
const GUEST_SHUTDOWN_TIMEOUT_SECS: u64 = 3;
fn install_graceful_shutdown_handler(transport: boxlite_shared::Transport) {
use signal_hook::consts::signal::SIGTERM;
use signal_hook::iterator::Signals;
let mut signals = match Signals::new([SIGTERM]) {
Ok(s) => s,
Err(e) => {
tracing::warn!("Failed to install SIGTERM handler: {e}");
return;
}
};
thread::spawn(move || {
for sig in signals.forever() {
if sig == SIGTERM {
tracing::info!("SIGTERM received, initiating graceful guest shutdown");
break;
}
}
match tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
{
Ok(rt) => {
let session = boxlite::GuestSession::new(transport);
let result = rt.block_on(async {
tokio::time::timeout(Duration::from_secs(GUEST_SHUTDOWN_TIMEOUT_SECS), async {
match session.guest().await {
Ok(mut guest) => {
let _ = guest.shutdown().await;
}
Err(e) => {
tracing::debug!("Could not connect to guest for shutdown: {e}");
}
}
})
.await
});
match result {
Ok(()) => tracing::info!("Guest shutdown completed (filesystems synced)"),
Err(_) => tracing::warn!(
timeout_secs = GUEST_SHUTDOWN_TIMEOUT_SECS,
"Guest shutdown timed out"
),
}
}
Err(e) => tracing::warn!("Failed to build tokio runtime for guest shutdown: {e}"),
}
unsafe {
libc::signal(libc::SIGTERM, libc::SIG_DFL);
libc::raise(libc::SIGTERM);
}
});
}
fn start_parent_watchdog() {
thread::spawn(|| {
let mut pollfd = libc::pollfd {
fd: watchdog::PIPE_FD,
events: libc::POLLIN, revents: 0,
};
let ret = unsafe { libc::poll(&mut pollfd, 1, -1) };
if ret > 0 && (pollfd.revents & libc::POLLHUP) != 0 {
tracing::info!("Parent death detected (POLLHUP on watchdog pipe)");
} else {
tracing::warn!(
ret = ret,
revents = pollfd.revents,
"Watchdog poll returned unexpectedly"
);
}
let self_pid = std::process::id();
unsafe {
libc::kill(self_pid as i32, libc::SIGTERM);
}
thread::sleep(Duration::from_secs(
GUEST_SHUTDOWN_TIMEOUT_SECS + GRACEFUL_SHUTDOWN_TIMEOUT_SECS,
));
tracing::warn!("Graceful shutdown timed out, forcing exit with SIGKILL");
unsafe {
libc::kill(self_pid as i32, libc::SIGKILL);
}
std::process::exit(137); });
}