use std::{
os::unix::ffi::OsStrExt,
process::{Command, ExitCode},
};
use nix::{
errno::Errno,
libc::pid_t,
sched::{setns, CloneFlags},
unistd::Pid,
};
use syd::{
config::SYD_SH,
confine::{run_cmd, CLONE_NEWTIME},
err::SydResult,
fd::pidfd_open,
path::{XPath, XPathBuf},
proc::proc_namespaces,
};
#[cfg(all(
not(coverage),
not(feature = "prof"),
not(target_os = "android"),
not(target_arch = "riscv64"),
target_page_size_4k,
target_pointer_width = "64"
))]
#[global_allocator]
static GLOBAL: hardened_malloc::HardenedMalloc = hardened_malloc::HardenedMalloc;
#[cfg(feature = "prof")]
#[global_allocator]
static GLOBAL: tcmalloc::TCMalloc = tcmalloc::TCMalloc;
syd::main! {
use lexopt::prelude::*;
syd::set_sigpipe_dfl()?;
let mut opt_cfl = CloneFlags::empty();
let mut opt_pid = None;
let mut opt_cmd = vec![];
let mut opt_log = false;
let mut parser = lexopt::Parser::from_env();
while let Some(arg) = parser.next()? {
match arg {
Short('h') => {
help();
return Ok(ExitCode::SUCCESS);
}
Short('a') => opt_cfl = CloneFlags::empty(),
Short('c') => opt_cfl |= CloneFlags::CLONE_NEWCGROUP,
Short('i') => opt_cfl |= CloneFlags::CLONE_NEWIPC,
Short('m') => opt_cfl |= CloneFlags::CLONE_NEWNS,
Short('n') => opt_cfl |= CloneFlags::CLONE_NEWNET,
Short('p') => opt_cfl |= CloneFlags::CLONE_NEWPID,
Short('t') => opt_cfl |= CLONE_NEWTIME,
Short('u') => opt_cfl |= CloneFlags::CLONE_NEWUTS,
Short('U') => opt_cfl |= CloneFlags::CLONE_NEWUSER,
Short('v') => opt_log = true,
Value(pid) => {
opt_pid = Some(pid);
opt_cmd.extend(parser.raw_args()?);
}
_ => return Err(arg.unexpected().into()),
}
}
let pid = if let Some(pid) = opt_pid {
let pid = pid.parse::<pid_t>()?;
if pid <= 0 {
return Err(Errno::EINVAL.into());
}
pid
} else {
help();
return Ok(ExitCode::FAILURE);
};
let namespaces = if opt_cfl.is_empty() {
match nsget(pid, opt_log) {
Ok(namespaces) => namespaces,
Err(errno) => {
eprintln!("syd-run: nsget: {errno}!");
return Ok(ExitCode::FAILURE);
}
}
} else {
opt_cfl
};
if !namespaces.is_empty() {
if let Err(errno) = nsenter(pid, namespaces) {
eprintln!("syd-run: nsenter: {errno}!");
return Ok(ExitCode::FAILURE);
}
}
if opt_cmd.is_empty() {
opt_cmd = vec![SYD_SH.into()];
}
let cmd = XPathBuf::from(opt_cmd.remove(0));
if opt_log {
eprintln!("syd-run: exec command `{cmd}'...",);
}
let mut cmd = Command::new(cmd);
let cmd = cmd.args(opt_cmd);
Ok(ExitCode::from(run_cmd(cmd)))
}
fn help() {
println!("Usage: syd-run [-hvacimnptuU] pid [<program> [<argument>...]]");
println!("Run a program inside a container (requires Linux-5.8 or newer).");
}
fn nsenter(pid: pid_t, namespaces: CloneFlags) -> Result<(), Errno> {
setns(pidfd_open(Pid::from_raw(pid), 0)?, namespaces)
}
fn nsget(pid: pid_t, log: bool) -> SydResult<CloneFlags> {
let current_pid = Pid::this();
let current_namespaces = proc_namespaces(current_pid)?;
let target_pid = Pid::from_raw(pid);
let target_namespaces = proc_namespaces(target_pid)?.0;
let mut flags = CloneFlags::empty();
for (name, target_ns) in target_namespaces {
if let Some(current_ns) = current_namespaces.0.get(&name) {
if target_ns.identifier != current_ns.identifier {
let name = name.as_bytes();
flags |= match name {
b"cgroup" => CloneFlags::CLONE_NEWCGROUP,
b"ipc" => CloneFlags::CLONE_NEWIPC,
b"mnt" => CloneFlags::CLONE_NEWNS,
b"net" => CloneFlags::CLONE_NEWNET,
b"user" => CloneFlags::CLONE_NEWUSER,
b"uts" => CloneFlags::CLONE_NEWUTS,
b"pid_for_children" => continue, b"time_for_children" => continue, _ => {
if log {
eprintln!(
"syd-run: skip unsupported {} namespace switch from id:{} to id:{}!",
XPath::from_bytes(name),
current_ns.identifier,
target_ns.identifier
);
}
continue;
}
};
if log {
eprintln!(
"syd-run: switch {} namespace from id:{} to id:{}...",
XPath::from_bytes(name),
current_ns.identifier,
target_ns.identifier
);
}
}
}
}
Ok(flags)
}