use nix::sys::signal::{SigHandler, Signal, signal};
use nix::unistd::execv;
use std::ffi::CString;
use std::process::exit;
pub struct SandboxInit {
pub program: String,
pub args: Vec<String>,
}
impl SandboxInit {
pub fn new(program: String, args: Vec<String>) -> Self {
Self { program, args }
}
pub fn run(&self) -> ! {
Self::setup_signals();
Self::mount_procfs();
Self::mount_sysfs();
self.exec_user_program();
}
fn setup_signals() {
unsafe {
let _ = signal(Signal::SIGCHLD, SigHandler::SigIgn);
let _ = signal(Signal::SIGTERM, SigHandler::SigDfl);
}
}
fn mount_procfs() {
let _ = std::fs::create_dir("/proc");
let source = CString::new("proc").unwrap();
let target = CString::new("/proc").unwrap();
let fstype = CString::new("proc").unwrap();
unsafe {
libc::mount(
source.as_ptr(),
target.as_ptr(),
fstype.as_ptr(),
0,
std::ptr::null(),
);
}
}
fn mount_sysfs() {
let _ = std::fs::create_dir("/sys");
let source = CString::new("sysfs").unwrap();
let target = CString::new("/sys").unwrap();
let fstype = CString::new("sysfs").unwrap();
unsafe {
libc::mount(
source.as_ptr(),
target.as_ptr(),
fstype.as_ptr(),
0,
std::ptr::null(),
);
}
}
fn exec_user_program(&self) -> ! {
let program_cstr = match CString::new(self.program.clone()) {
Ok(s) => s,
Err(_) => {
eprintln!("Invalid program name");
exit(1);
}
};
let args_cstr: Vec<CString> = self
.args
.iter()
.map(|arg| CString::new(arg.clone()).unwrap_or_else(|_| CString::new("").unwrap()))
.collect();
let args_refs: Vec<&CString> = vec![&program_cstr]
.into_iter()
.chain(args_cstr.iter())
.collect();
match execv(&program_cstr, &args_refs) {
Ok(_) => {
exit(0);
}
Err(e) => {
eprintln!("Failed to execute program: {}", e);
exit(1);
}
}
}
pub fn reap_children() {
use nix::sys::wait::{WaitStatus, waitpid};
use nix::unistd::Pid;
loop {
match waitpid(
Pid::from_raw(-1),
Some(nix::sys::wait::WaitPidFlag::WNOHANG),
) {
Ok(WaitStatus::Exited(pid, _status)) => {
eprintln!("[init] Child {} exited", pid);
}
Ok(WaitStatus::Signaled(pid, signal, _core)) => {
eprintln!("[init] Child {} killed by {:?}", pid, signal);
}
Ok(WaitStatus::StillAlive) => break,
Ok(_) => continue,
Err(_) => break,
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_init_creation() {
let init = SandboxInit::new("/bin/echo".to_string(), vec!["hello".to_string()]);
assert_eq!(init.program, "/bin/echo");
assert_eq!(init.args.len(), 1);
}
#[test]
fn test_init_empty_args() {
let init = SandboxInit::new("/bin/sh".to_string(), Vec::new());
assert!(init.args.is_empty());
}
#[test]
fn test_mount_helpers_are_best_effort() {
SandboxInit::mount_procfs();
SandboxInit::mount_sysfs();
}
}