#![cfg(target_os = "linux")]
use std::fs;
use std::io;
use std::os::unix::process::CommandExt;
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct CgroupName {
mount_point: PathBuf,
name: PathBuf,
}
impl CgroupName {
pub fn new<P>(name: P) -> Self
where
P: AsRef<Path>,
{
Self {
mount_point: "/sys/fs/cgroup".into(),
name: name.as_ref().to_path_buf(),
}
}
}
#[derive(Debug)]
pub struct Cgroup {
root: PathBuf,
}
impl Cgroup {
pub fn new(cgroup_name: &CgroupName, subsystem: &str) -> Self {
Self {
root: cgroup_name
.mount_point
.join(subsystem)
.join(&cgroup_name.name),
}
}
pub fn create(&self) -> io::Result<()> {
fs::create_dir(&self.root)
}
pub fn remove(&self) -> io::Result<()> {
fs::remove_dir(&self.root)
}
pub fn set_raw_value<V>(&self, key: &str, value: V) -> io::Result<()>
where
V: AsRef<[u8]>,
{
fs::write(self.root.join(key), value)
}
pub fn set_value<V>(&self, key: &str, value: V) -> io::Result<()>
where
V: Copy + ToString,
{
self.set_raw_value(key, value.to_string())
}
pub fn get_raw_value(&self, key: &str) -> io::Result<String> {
fs::read_to_string(self.root.join(key))
}
pub fn get_value<T>(&self, key: &str) -> io::Result<T>
where
T: std::str::FromStr,
{
self.get_raw_value(key)?
.trim_end()
.parse()
.map_err(|_| io::Error::new(io::ErrorKind::Other, "could not parse the value"))
}
fn tasks_absolute_path(&self) -> PathBuf {
self.root.join("tasks")
}
pub fn add_task(&self, pid: nix::unistd::Pid) -> io::Result<()> {
fs::write(self.tasks_absolute_path(), pid.to_string())
}
pub fn get_tasks(&self) -> io::Result<Vec<nix::unistd::Pid>> {
Ok(fs::read_to_string(self.tasks_absolute_path())?
.split_whitespace()
.map(|pid| nix::unistd::Pid::from_raw(pid.parse().unwrap()))
.collect())
}
pub fn kill_all_tasks(&self) -> io::Result<()> {
for _ in 0..100 {
let tasks = self.get_tasks()?;
if tasks.is_empty() {
return Ok(());
}
for task in tasks {
nix::sys::signal::kill(task, nix::sys::signal::Signal::SIGKILL).is_ok();
}
std::thread::sleep(std::time::Duration::from_micros(1));
}
Err(io::Error::new(
io::ErrorKind::Other,
"child subprocess(es) survived SIGKILL",
))
}
}
pub struct AutomanagedCgroup {
inner: Cgroup,
}
impl AutomanagedCgroup {
pub fn init(cgroup_name: &CgroupName, subsystem: &str) -> io::Result<Self> {
let inner = Cgroup::new(cgroup_name, subsystem);
if let Err(error) = inner.create() {
match inner.get_tasks() {
Err(_) => return Err(error),
Ok(tasks) => {
if !tasks.is_empty() {
return Err(error);
}
}
}
inner.remove().is_ok();
inner.create()?;
}
Ok(Self { inner })
}
}
impl std::ops::Deref for AutomanagedCgroup {
type Target = Cgroup;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl AsRef<Cgroup> for AutomanagedCgroup {
fn as_ref(&self) -> &Cgroup {
&self
}
}
impl Drop for AutomanagedCgroup {
fn drop(&mut self) {
drop(self.inner.remove());
}
}
pub trait CgroupsCommandExt {
fn cgroups(&mut self, cgroups: &[impl AsRef<Cgroup>]) -> &mut Self;
}
impl CgroupsCommandExt for std::process::Command {
fn cgroups(&mut self, cgroups: &[impl AsRef<Cgroup>]) -> &mut Self {
let tasks_paths = cgroups
.iter()
.map(|cgroup| cgroup.as_ref().tasks_absolute_path())
.collect::<Vec<PathBuf>>();
self.before_exec(move || {
let pid = std::process::id().to_string();
for tasks_path in &tasks_paths {
fs::write(tasks_path, &pid)?;
}
Ok(())
})
}
}