#![cfg(target_os = "linux")]
#![deny(missing_docs)]
use std::fs;
use std::io;
use std::os::unix::process::CommandExt;
use std::path::{Path, PathBuf};
use nix;
#[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).map_err(|error| {
io::Error::new(
error.kind(),
format!(
"Cgroup cannot be created due to: {} (tried creating {:?} directory)",
error, self.root
),
)
})
}
pub fn remove(&self) -> io::Result<()> {
fs::remove_dir(&self.root).map_err(|error| {
io::Error::new(
error.kind(),
format!(
"Cgroup cannot be removed due to: {} (tried removing {:?} directory)",
error, self.root
),
)
})
}
pub fn set_raw_value<V>(&self, key: &str, value: V) -> io::Result<()>
where
V: AsRef<[u8]>,
{
let key = self.root.join(key);
fs::write(&key, value).map_err(|error| {
io::Error::new(
error.kind(),
format!(
"Cgroup value under key {:?} cannot be set due to: {}",
key, error
),
)
})
}
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> {
let key = self.root.join(key);
fs::read_to_string(&key).map_err(|error| {
io::Error::new(
error.kind(),
format!(
"Cgroup value under key {:?} cannot be read due to: {}",
key, error
),
)
})
}
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()).map_err(|error| {
io::Error::new(
error.kind(),
format!(
"A task cannot be added to cgroup {:?} due to: {}",
self.root, error
),
)
})
}
pub fn get_tasks(&self) -> io::Result<Vec<nix::unistd::Pid>> {
Ok(fs::read_to_string(self.tasks_absolute_path())
.map_err(|error| {
io::Error::new(
error.kind(),
format!(
"Tasks cannot be read from cgroup {:?} due to: {}",
self.root, error
),
)
})?
.split_whitespace()
.map(|pid| nix::unistd::Pid::from_raw(pid.parse().unwrap()))
.collect())
}
pub fn send_signal_to_all_tasks(&self, signal: nix::sys::signal::Signal) -> io::Result<usize> {
let tasks = self.get_tasks()?;
let tasks_count = tasks.len();
for task in tasks {
nix::sys::signal::kill(task, signal).ok();
}
Ok(tasks_count)
}
#[deprecated(
since = "1.0.1",
note = "please, use `freezer` cgroup to implement `kill_all_tasks` reliably (https://gitlab.com/dots.org.ua/ddots-runner/blob/d967ee3ba9de364dfb5a2e1a4f468586efb504f8/src/extensions/process.rs#L132-166)"
)]
pub fn kill_all_tasks(&self) -> io::Result<()> {
for _ in 0..100 {
if self.send_signal_to_all_tasks(nix::sys::signal::Signal::SIGKILL)? == 0 {
break;
}
std::thread::sleep(std::time::Duration::from_micros(1));
}
Err(io::Error::new(
io::ErrorKind::Other,
"child subprocess(es) survived SIGKILL",
))
}
}
#[derive(Debug)]
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().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>>();
unsafe {
self.pre_exec(move || {
let pid = std::process::id().to_string();
for tasks_path in &tasks_paths {
fs::write(tasks_path, &pid)?;
}
Ok(())
})
}
}
}