use std::cmp;
use std::error::Error;
use std::ffi::{OsStr, OsString};
use std::fs::{self, OpenOptions};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::result;
use std::str::FromStr;
use std::time::Duration;
use config::{ClearUsage, ControllerPath, Limits, SpaceUsage};
use errors::CGroupError;
use ffi;
use run_info::RunUsage;
type Result<T> = result::Result<T, CGroupError>;
fn cgroup_write<T1: AsRef<Path>, T2: AsRef<str>>(
controller_path: &Path,
file: T1,
line: T2,
) -> Result<()> {
let path = controller_path.join(file.as_ref());
let mut cgroup_file = OpenOptions::new().write(true).open(&path).map_err(|err| {
CGroupError::OpenCGroupFileError {
controller_path: controller_path.to_path_buf(),
file: file.as_ref().to_path_buf(),
error: err.description().into(),
}
})?;
cgroup_file
.write_all(line.as_ref().as_bytes())
.map_err(|err| CGroupError::WriteCGroupFileError {
controller_path: controller_path.to_path_buf(),
file: file.as_ref().to_path_buf(),
error: err.description().into(),
})
}
fn cgroup_read<T1: AsRef<Path>, T2: FromStr>(controller_path: &Path, file: T1) -> Result<T2>
where
<T2 as FromStr>::Err: Error,
{
let path = controller_path.join(file.as_ref());
let mut cgroup_file = OpenOptions::new().read(true).open(&path).map_err(|err| {
CGroupError::OpenCGroupFileError {
controller_path: controller_path.to_path_buf(),
file: file.as_ref().to_path_buf(),
error: err.description().into(),
}
})?;
let mut buffer = String::new();
let _ = cgroup_file.read_to_string(&mut buffer).map_err(|err| {
CGroupError::ReadCGroupFileError {
controller_path: controller_path.to_path_buf(),
file: file.as_ref().to_path_buf(),
error: err.description().into(),
}
})?;
buffer
.trim()
.parse::<T2>()
.map_err(|err| CGroupError::ParseCGroupFileError {
controller_path: controller_path.to_path_buf(),
file: file.as_ref().to_path_buf(),
buffer,
error: err.description().into(),
})
}
const ISOLATED_CGROUP_NAME: &str = "isolated";
pub(crate) fn enter_cgroup(controller_path: &Path) -> Result<()> {
let isolated_cgroup = controller_path.join(ISOLATED_CGROUP_NAME);
if !isolated_cgroup.exists() {
fs::create_dir(&isolated_cgroup).map_err(|err| {
CGroupError::InstanceControllerCreateError {
controller_path: controller_path.to_path_buf(),
instance_name: OsString::from(ISOLATED_CGROUP_NAME),
error: err.description().into(),
}
})?;
}
cgroup_write(&isolated_cgroup, "tasks", format!("{}\n", ffi::getpid()))
}
const DEFAULT_INSTANCE_NAME: &str = "default";
fn get_instance_path(controller_path: &Path, instance_name: Option<&OsStr>) -> Result<PathBuf> {
let instance = instance_name.unwrap_or_else(|| OsStr::new(DEFAULT_INSTANCE_NAME));
if !controller_path.exists() {
return Err(CGroupError::ControllerMissing(
controller_path.to_path_buf(),
));
}
let instance_path = controller_path.join(instance);
if !instance_path.exists() {
fs::create_dir(&instance_path).map_err(|err| CGroupError::InstanceControllerCreateError {
controller_path: controller_path.to_path_buf(),
instance_name: instance.to_os_string(),
error: err.description().into(),
})?;
}
Ok(instance_path)
}
const CPUACCT_DEFAULT_CONTROLLER_PATH: &str = "/sys/fs/cgroup/cpuacct/ia-sandbox";
pub(crate) fn enter_cpuacct_cgroup(
controller_path: Option<&Path>,
instance_name: Option<&OsStr>,
clear_usage: ClearUsage,
) -> Result<()> {
let instance_path = get_instance_path(
controller_path.unwrap_or_else(|| Path::new(CPUACCT_DEFAULT_CONTROLLER_PATH)),
instance_name,
)?;
if clear_usage == ClearUsage::Yes {
cgroup_write(&instance_path, "cpuacct.usage", "0\n")?;
}
enter_cgroup(&instance_path)
}
const MEMORY_DEFAULT_CONTROLLER_PATH: &str = "/sys/fs/cgroup/memory/ia-sandbox";
const EXTRA_MEMORY_GIVEN: u64 = 16 * 1_024;
pub(crate) fn enter_memory_cgroup(
controller_path: Option<&Path>,
instance_name: Option<&OsStr>,
memory_limit: Option<SpaceUsage>,
clear_usage: ClearUsage,
) -> Result<()> {
let instance_path = get_instance_path(
controller_path.unwrap_or_else(|| Path::new(MEMORY_DEFAULT_CONTROLLER_PATH)),
instance_name,
)?;
if clear_usage == ClearUsage::Yes {
cgroup_write(&instance_path, "memory.max_usage_in_bytes", "0\n")?;
cgroup_write(&instance_path, "memory.memsw.max_usage_in_bytes", "0\n").unwrap_or(());
cgroup_write(&instance_path, "memory.memsw.limit_in_bytes", "-1\n").unwrap_or(());
cgroup_write(&instance_path, "memory.limit_in_bytes", "-1\n")?;
if let Some(memory_limit) = memory_limit {
cgroup_write(
&instance_path,
"memory.limit_in_bytes",
format!("{}\n", memory_limit.as_bytes() + EXTRA_MEMORY_GIVEN),
)?;
cgroup_write(
&instance_path,
"memory.memsw.limit_in_bytes",
format!("{}\n", memory_limit.as_bytes() + EXTRA_MEMORY_GIVEN),
).unwrap_or(());
}
}
enter_cgroup(&instance_path)
}
const PIDS_DEFAULT_CONTROLLER_PATH: &str = "/sys/fs/cgroup/pids/ia-sandbox";
pub(crate) fn enter_pids_cgroup(
controller_path: Option<&Path>,
instance_name: Option<&OsStr>,
pids_limit: Option<usize>,
clear_usage: ClearUsage,
) -> Result<()> {
let instance_path = get_instance_path(
controller_path.unwrap_or_else(|| Path::new(PIDS_DEFAULT_CONTROLLER_PATH)),
instance_name,
)?;
if clear_usage == ClearUsage::Yes {
if let Some(pids_limit) = pids_limit {
cgroup_write(&instance_path, "pids.max", format!("{}\n", pids_limit))?;
} else {
cgroup_write(&instance_path, "pids.max", "max\n")?;
}
}
enter_cgroup(&instance_path)
}
pub(crate) fn enter_all_cgroups(
controller_path: &ControllerPath,
instance_name: Option<&OsStr>,
limits: Limits,
clear_usage: ClearUsage,
) -> Result<()> {
enter_cpuacct_cgroup(controller_path.cpuacct(), instance_name, clear_usage)?;
enter_memory_cgroup(
controller_path.memory(),
instance_name,
limits.memory(),
clear_usage,
)?;
enter_pids_cgroup(
controller_path.pids(),
instance_name,
limits.pids(),
clear_usage,
)
}
pub(crate) fn get_usage(
controller_path: &ControllerPath,
instance_name: Option<&OsStr>,
wall_time: Duration,
) -> Result<RunUsage> {
let cpuacct_controller_path = controller_path
.cpuacct()
.unwrap_or_else(|| Path::new(CPUACCT_DEFAULT_CONTROLLER_PATH));
let memory_controller_path = controller_path
.memory()
.unwrap_or_else(|| Path::new(MEMORY_DEFAULT_CONTROLLER_PATH));
let instance = instance_name.unwrap_or_else(|| OsStr::new(DEFAULT_INSTANCE_NAME));
let cpuacct_instance_path = cpuacct_controller_path.join(instance);
let user_time = Duration::from_nanos(cgroup_read(&cpuacct_instance_path, "cpuacct.usage")?);
let memory_instance_path = memory_controller_path.join(instance);
let memory = SpaceUsage::from_bytes(cmp::max(
cgroup_read(&memory_instance_path, "memory.max_usage_in_bytes")?,
cgroup_read(&memory_instance_path, "memory.memsw.max_usage_in_bytes").unwrap_or(0),
));
Ok(RunUsage::new(user_time, wall_time, memory))
}