use pelagos::sandbox::{create_sandbox, list_sandboxes, remove_sandbox, SandboxState};
#[derive(Debug, clap::Subcommand)]
pub enum SandboxCmd {
Create {
#[clap(long)]
name: Option<String>,
},
Ls {
#[clap(long)]
json: bool,
},
Rm {
id: String,
},
#[clap(hide = true, name = "__pause__")]
Pause {
ns_name: String,
#[clap(long = "host-ipc")]
host_ipc: bool,
#[clap(long = "host-net")]
host_net: bool,
#[clap(long = "pod-pid")]
pod_pid: bool,
},
}
pub fn cmd_sandbox(cmd: SandboxCmd) -> Result<(), Box<dyn std::error::Error>> {
match cmd {
SandboxCmd::Create { name } => cmd_sandbox_create(name.as_deref()),
SandboxCmd::Ls { json } => cmd_sandbox_ls(json),
SandboxCmd::Rm { id } => cmd_sandbox_rm(&id),
SandboxCmd::Pause {
ns_name,
host_ipc,
host_net,
pod_pid,
} => cmd_sandbox_pause(&ns_name, host_ipc, host_net, pod_pid),
}
}
fn cmd_sandbox_create(name: Option<&str>) -> Result<(), Box<dyn std::error::Error>> {
let state = create_sandbox(name)?;
println!("{}", state.id);
Ok(())
}
fn cmd_sandbox_ls(json: bool) -> Result<(), Box<dyn std::error::Error>> {
let sandboxes = list_sandboxes();
if json {
let alive: Vec<&SandboxState> = sandboxes.iter().filter(|s| s.is_alive()).collect();
println!(
"{}",
serde_json::to_string_pretty(&alive)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?
);
return Ok(());
}
if sandboxes.is_empty() {
return Ok(());
}
println!(
"{:<18} {:<20} {:<8} {:<16} NS_NAME",
"ID", "NAME", "STATUS", "IP"
);
for s in &sandboxes {
let status = if s.is_alive() { "running" } else { "dead" };
let name = s.name.as_deref().unwrap_or("-");
println!(
"{:<18} {:<20} {:<8} {:<16} {}",
s.id, name, status, s.container_ip, s.ns_name
);
}
Ok(())
}
fn cmd_sandbox_rm(id: &str) -> Result<(), Box<dyn std::error::Error>> {
remove_sandbox(id)?;
Ok(())
}
fn cmd_sandbox_pause(
ns_name: &str,
host_ipc: bool,
host_net: bool,
pod_pid: bool,
) -> Result<(), Box<dyn std::error::Error>> {
if !host_net {
let netns_path = format!("/run/netns/{}", ns_name);
let netns_c = std::ffi::CString::new(netns_path.as_bytes())
.map_err(|e| format!("invalid netns path: {}", e))?;
let fd = unsafe { libc::open(netns_c.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC) };
if fd < 0 {
return Err(format!(
"open netns '{}': {}",
netns_path,
std::io::Error::last_os_error()
)
.into());
}
let rc = unsafe { libc::setns(fd, libc::CLONE_NEWNET) };
unsafe { libc::close(fd) };
if rc != 0 {
return Err(format!(
"setns netns '{}': {}",
netns_path,
std::io::Error::last_os_error()
)
.into());
}
}
let unshare_flags = if host_ipc {
libc::CLONE_NEWUTS
} else {
libc::CLONE_NEWIPC | libc::CLONE_NEWUTS
};
let rc = unsafe { libc::unshare(unshare_flags) };
if rc != 0 {
return Err(format!("unshare UTS/IPC: {}", std::io::Error::last_os_error()).into());
}
if pod_pid {
let rc = unsafe { libc::unshare(libc::CLONE_NEWPID) };
if rc != 0 {
return Err(format!("unshare PID: {}", std::io::Error::last_os_error()).into());
}
let child = unsafe { libc::fork() };
if child < 0 {
return Err(format!("fork pod-pid init: {}", std::io::Error::last_os_error()).into());
}
if child == 0 {
run_pid1_init_loop();
}
let mut status: libc::c_int = 0;
loop {
let r = unsafe { libc::waitpid(child, &mut status, 0) };
if r == child
|| (r < 0 && std::io::Error::last_os_error().raw_os_error() != Some(libc::EINTR))
{
break;
}
}
return Ok(());
}
loop {
unsafe { libc::pause() };
}
}
fn run_pid1_init_loop() -> ! {
use nix::sys::signal::{signal, SigHandler, Signal};
extern "C" fn on_term(_: libc::c_int) {
unsafe { libc::_exit(0) };
}
extern "C" fn on_chld(_: libc::c_int) {}
unsafe {
let _ = signal(Signal::SIGTERM, SigHandler::Handler(on_term));
let _ = signal(Signal::SIGCHLD, SigHandler::Handler(on_chld));
}
loop {
let mut status: libc::c_int = 0;
while unsafe { libc::waitpid(-1, &mut status, libc::WNOHANG) } > 0 {}
unsafe { libc::pause() };
}
}