use nix::sched::{CloneFlags, setns, unshare};
use std::fs::File;
use std::os::unix::io::AsFd;
use std::sync::Arc;
use tokio::runtime::{Builder, Runtime};
use tokio::task::JoinHandle;
#[derive(Clone)]
pub struct HostNamespaceExecutor {
host_rt: Option<Arc<Runtime>>,
}
impl Drop for HostNamespaceExecutor {
fn drop(&mut self) {
if self.host_rt.is_some()
&& Arc::strong_count(self.host_rt.as_ref().expect("just checked")) <= 1
&& let Some(rt) = self.host_rt.take()
{
Arc::into_inner(rt).unwrap().shutdown_background();
}
}
}
impl HostNamespaceExecutor {
pub fn new() -> Self {
if !Self::in_host_pid_namespace() {
panic!("Cannot create host namespace executor, not running in host pid namespace");
}
Self::runtime_in_target_process_namespaces(1)
}
pub fn spawn_in_host_ns<F>(&self, f: F) -> JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
self.host_rt
.as_ref()
.expect("host executor dropped")
.spawn(f)
}
fn runtime_in_target_process_namespaces(target_pid: u32) -> Self {
let rt = std::thread::spawn(move || {
unshare(CloneFlags::CLONE_FS).expect("Failed to unshare FS attributes");
for (ns, flag) in &[
("mnt", CloneFlags::CLONE_NEWNS),
("net", CloneFlags::CLONE_NEWNET),
("uts", CloneFlags::CLONE_NEWUTS),
("ipc", CloneFlags::CLONE_NEWIPC),
("pid", CloneFlags::CLONE_NEWPID),
] {
let f = File::open(format!("/proc/{}/ns/{}", target_pid, ns))
.expect("could not open pid {target_pid} ns file {ns}");
setns(f.as_fd(), *flag)
.unwrap_or_else(|_| panic!("could not setns for pid {target_pid} ns {ns}"));
}
Builder::new_multi_thread()
.thread_name(format!("pid-{target_pid}-executor-threadset"))
.build()
.expect("could not spawn pid-{target_pid} threadset")
})
.join()
.expect("could not create pid-{target_pid} executor");
Self {
host_rt: Some(Arc::new(rt)),
}
}
fn in_host_pid_namespace() -> bool {
if !std::path::Path::new("/proc/2").exists() {
return false; }
let comm = std::fs::read_to_string("/proc/2/comm").expect("can read proc");
comm.trim() == "kthreadd"
}
}