diff --git a/src/blkio.rs b/src/fs/blkio.rs similarity index 99% rename from src/blkio.rs rename to src/fs/blkio.rs index 452482b..66aff98 100644 --- a/src/blkio.rs +++ b/src/fs/blkio.rs @@ -11,11 +11,11 @@ use std::io::Write; use std::path::PathBuf; -use crate::error::ErrorKind::*; -use crate::error::*; +use crate::fs::error::ErrorKind::*; +use crate::fs::error::*; -use crate::{read_string_from, read_u64_from}; -use crate::{ +use crate::fs::{read_string_from, read_u64_from}; +use crate::fs::{ BlkIoResources, ControllIdentifier, ControllerInternal, Controllers, CustomizedAttribute, Resources, Subsystem, }; @@ -829,9 +829,9 @@ impl BlkIoController { impl CustomizedAttribute for BlkIoController {} #[cfg(test)] mod test { - use crate::blkio::{parse_blkio_data, BlkIoData}; - use crate::blkio::{parse_io_service, parse_io_service_total, IoService}; - use crate::error::*; + use crate::fs::blkio::{parse_blkio_data, BlkIoData}; + use crate::fs::blkio::{parse_io_service, parse_io_service_total, IoService}; + use crate::fs::error::*; static TEST_VALUE: &str = "\ 8:32 Read 4280320 diff --git a/src/cgroup.rs b/src/fs/cgroup.rs similarity index 98% rename from src/cgroup.rs rename to src/fs/cgroup.rs index 6e0b9d0..ef9aade 100644 --- a/src/cgroup.rs +++ b/src/fs/cgroup.rs @@ -6,11 +6,11 @@ //! This module handles cgroup operations. Start here! -use crate::error::ErrorKind::*; -use crate::error::*; +use crate::fs::error::ErrorKind::*; +use crate::fs::error::*; -use crate::hierarchies::V1; -use crate::{CgroupPid, ControllIdentifier, Controller, Hierarchy, Resources, Subsystem}; +use crate::fs::hierarchies::V1; +use crate::fs::{CgroupPid, ControllIdentifier, Controller, Hierarchy, Resources, Subsystem}; use std::collections::HashMap; use std::convert::From; @@ -51,7 +51,7 @@ impl Clone for Cgroup { fn clone(&self) -> Self { Cgroup { subsystems: self.subsystems.clone(), - hier: crate::hierarchies::auto(), + hier: crate::fs::hierarchies::auto(), path: self.path.clone(), specified_controllers: None, } @@ -62,7 +62,7 @@ impl Default for Cgroup { fn default() -> Self { Cgroup { subsystems: Vec::new(), - hier: crate::hierarchies::auto(), + hier: crate::fs::hierarchies::auto(), path: "".to_string(), specified_controllers: None, } diff --git a/src/cgroup_builder.rs b/src/fs/cgroup_builder.rs similarity index 97% rename from src/cgroup_builder.rs rename to src/fs/cgroup_builder.rs index a453d04..094e3d1 100644 --- a/src/cgroup_builder.rs +++ b/src/fs/cgroup_builder.rs @@ -16,10 +16,10 @@ //! by a call to `build()`. //! //! ```rust,no_run -//! # use cgroups_rs::*; -//! # use cgroups_rs::devices::*; -//! # use cgroups_rs::cgroup_builder::*; -//! let h = cgroups_rs::hierarchies::auto(); +//! # use cgroups_rs::fs::*; +//! # use cgroups_rs::fs::devices::*; +//! # use cgroups_rs::fs::cgroup_builder::*; +//! let h = cgroups_rs::fs::hierarchies::auto(); //! let cgroup: Cgroup = CgroupBuilder::new("hello") //! .memory() //! .kernel_memory_limit(1024 * 1024) @@ -60,7 +60,7 @@ //! .build(h).unwrap(); //! ``` -use crate::{ +use crate::fs::{ BlkIoDeviceResource, BlkIoDeviceThrottleResource, Cgroup, DeviceResource, Error, Hierarchy, HugePageResource, MaxValue, NetworkPriority, Resources, }; @@ -250,9 +250,9 @@ impl DeviceResourceBuilder { mut self, major: i64, minor: i64, - devtype: crate::devices::DeviceType, + devtype: crate::fs::devices::DeviceType, allow: bool, - access: Vec, + access: Vec, ) -> DeviceResourceBuilder { self.cgroup.resources.devices.devices.push(DeviceResource { allow, diff --git a/src/cpu.rs b/src/fs/cpu.rs similarity index 98% rename from src/cpu.rs rename to src/fs/cpu.rs index 50a334d..468ac5f 100644 --- a/src/cpu.rs +++ b/src/fs/cpu.rs @@ -13,11 +13,11 @@ use std::fs::File; use std::io::{Read, Write}; use std::path::PathBuf; -use crate::error::ErrorKind::*; -use crate::error::*; -use crate::{parse_max_value, read_i64_from, read_u64_from}; +use crate::fs::error::ErrorKind::*; +use crate::fs::error::*; +use crate::fs::{parse_max_value, read_i64_from, read_u64_from}; -use crate::{ +use crate::fs::{ ControllIdentifier, ControllerInternal, Controllers, CpuResources, CustomizedAttribute, MaxValue, Resources, Subsystem, }; diff --git a/src/cpuacct.rs b/src/fs/cpuacct.rs similarity index 96% rename from src/cpuacct.rs rename to src/fs/cpuacct.rs index ee6ba3e..aa67d37 100644 --- a/src/cpuacct.rs +++ b/src/fs/cpuacct.rs @@ -10,11 +10,11 @@ use std::io::Write; use std::path::PathBuf; -use crate::error::ErrorKind::*; -use crate::error::*; +use crate::fs::error::ErrorKind::*; +use crate::fs::error::*; -use crate::{read_string_from, read_u64_from}; -use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; +use crate::fs::{read_string_from, read_u64_from}; +use crate::fs::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; /// A controller that allows controlling the `cpuacct` subsystem of a Cgroup. /// diff --git a/src/cpuset.rs b/src/fs/cpuset.rs similarity index 99% rename from src/cpuset.rs rename to src/fs/cpuset.rs index 5a9e7dc..71435a9 100644 --- a/src/cpuset.rs +++ b/src/fs/cpuset.rs @@ -13,11 +13,11 @@ use log::*; use std::io::Write; use std::path::PathBuf; -use crate::error::ErrorKind::*; -use crate::error::*; +use crate::fs::error::ErrorKind::*; +use crate::fs::error::*; -use crate::{read_string_from, read_u64_from}; -use crate::{ +use crate::fs::{read_string_from, read_u64_from}; +use crate::fs::{ ControllIdentifier, ControllerInternal, Controllers, CpuResources, Resources, Subsystem, }; @@ -591,7 +591,7 @@ impl CpuSetController { #[cfg(test)] mod tests { - use crate::cpuset; + use crate::fs::cpuset; #[test] fn test_parse_range() { let test_cases = vec![ diff --git a/src/devices.rs b/src/fs/devices.rs similarity index 99% rename from src/devices.rs rename to src/fs/devices.rs index 56c11c5..02a0cf3 100644 --- a/src/devices.rs +++ b/src/fs/devices.rs @@ -12,10 +12,10 @@ use std::path::PathBuf; use log::*; -use crate::error::ErrorKind::*; -use crate::error::*; +use crate::fs::error::ErrorKind::*; +use crate::fs::error::*; -use crate::{ +use crate::fs::{ ControllIdentifier, ControllerInternal, Controllers, DeviceResource, DeviceResources, Resources, Subsystem, }; diff --git a/src/error.rs b/src/fs/error.rs similarity index 100% rename from src/error.rs rename to src/fs/error.rs diff --git a/src/events.rs b/src/fs/events.rs similarity index 97% rename from src/events.rs rename to src/fs/events.rs index 65506c0..af93db0 100644 --- a/src/events.rs +++ b/src/fs/events.rs @@ -12,8 +12,8 @@ use std::path::Path; use std::sync::mpsc::{self, Receiver}; use std::thread; -use crate::error::ErrorKind::*; -use crate::error::*; +use crate::fs::error::ErrorKind::*; +use crate::fs::error::*; // notify_on_oom returns channel on which you can expect event about OOM, // if process died without OOM this channel will be closed. diff --git a/src/freezer.rs b/src/fs/freezer.rs similarity index 96% rename from src/freezer.rs rename to src/fs/freezer.rs index 0b1bf6c..907dffb 100644 --- a/src/freezer.rs +++ b/src/fs/freezer.rs @@ -11,10 +11,10 @@ use std::io::{Read, Write}; use std::path::PathBuf; -use crate::error::ErrorKind::*; -use crate::error::*; +use crate::fs::error::ErrorKind::*; +use crate::fs::error::*; -use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; +use crate::fs::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; /// A controller that allows controlling the `freezer` subsystem of a Cgroup. /// diff --git a/src/hierarchies.rs b/src/fs/hierarchies.rs similarity index 94% rename from src/hierarchies.rs rename to src/fs/hierarchies.rs index 3c9911c..b0fb1c3 100644 --- a/src/hierarchies.rs +++ b/src/fs/hierarchies.rs @@ -11,23 +11,23 @@ use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; -use crate::blkio::BlkIoController; -use crate::cpu::CpuController; -use crate::cpuacct::CpuAcctController; -use crate::cpuset::CpuSetController; -use crate::devices::DevicesController; -use crate::freezer::FreezerController; -use crate::hugetlb::HugeTlbController; -use crate::memory::MemController; -use crate::net_cls::NetClsController; -use crate::net_prio::NetPrioController; -use crate::perf_event::PerfEventController; -use crate::pid::PidController; -use crate::rdma::RdmaController; -use crate::systemd::SystemdController; -use crate::{Controllers, Hierarchy, Subsystem}; +use crate::fs::blkio::BlkIoController; +use crate::fs::cpu::CpuController; +use crate::fs::cpuacct::CpuAcctController; +use crate::fs::cpuset::CpuSetController; +use crate::fs::devices::DevicesController; +use crate::fs::freezer::FreezerController; +use crate::fs::hugetlb::HugeTlbController; +use crate::fs::memory::MemController; +use crate::fs::net_cls::NetClsController; +use crate::fs::net_prio::NetPrioController; +use crate::fs::perf_event::PerfEventController; +use crate::fs::pid::PidController; +use crate::fs::rdma::RdmaController; +use crate::fs::systemd::SystemdController; +use crate::fs::{Controllers, Hierarchy, Subsystem}; -use crate::cgroup::Cgroup; +use crate::fs::cgroup::Cgroup; /// Process mounts information. /// diff --git a/src/hugetlb.rs b/src/fs/hugetlb.rs similarity index 98% rename from src/hugetlb.rs rename to src/fs/hugetlb.rs index 311dca1..06a7d28 100644 --- a/src/hugetlb.rs +++ b/src/fs/hugetlb.rs @@ -12,11 +12,11 @@ use log::warn; use std::io::Write; use std::path::PathBuf; -use crate::error::ErrorKind::*; -use crate::error::*; -use crate::{flat_keyed_to_vec, read_u64_from}; +use crate::fs::error::ErrorKind::*; +use crate::fs::error::*; +use crate::fs::{flat_keyed_to_vec, read_u64_from}; -use crate::{ +use crate::fs::{ ControllIdentifier, ControllerInternal, Controllers, HugePageResources, Resources, Subsystem, }; diff --git a/src/memory.rs b/src/fs/memory.rs similarity index 99% rename from src/memory.rs rename to src/fs/memory.rs index 653006d..6b0c376 100644 --- a/src/memory.rs +++ b/src/fs/memory.rs @@ -14,14 +14,14 @@ use std::io::Write; use std::path::PathBuf; use std::sync::mpsc::Receiver; -use crate::error::ErrorKind::*; -use crate::error::*; -use crate::events; -use crate::{read_i64_from, read_string_from, read_u64_from}; +use crate::fs::error::ErrorKind::*; +use crate::fs::error::*; +use crate::fs::events; +use crate::fs::{read_i64_from, read_string_from, read_u64_from}; -use crate::flat_keyed_to_hashmap; +use crate::fs::flat_keyed_to_hashmap; -use crate::{ +use crate::fs::{ ControllIdentifier, ControllerInternal, Controllers, CustomizedAttribute, MaxValue, MemoryResources, Resources, Subsystem, }; @@ -1002,7 +1002,7 @@ impl<'a> From<&'a Subsystem> for &'a MemController { #[cfg(test)] mod tests { - use crate::memory::{ + use crate::fs::memory::{ parse_memory_stat, parse_numa_stat, parse_oom_control, MemoryStat, NumaStat, OomControl, }; diff --git a/src/fs/mod.rs b/src/fs/mod.rs new file mode 100644 index 0000000..81aa115 --- /dev/null +++ b/src/fs/mod.rs @@ -0,0 +1,1032 @@ +// Copyright (c) 2018 Levente Kurusa +// Copyright (c) 2020 Ant Group +// +// SPDX-License-Identifier: Apache-2.0 or MIT +// + +#![allow(clippy::unnecessary_unwrap)] +use log::*; + +use std::collections::HashMap; +use std::fmt; +use std::fs::{self, File}; +use std::io::{BufRead, BufReader, Read, Write}; +use std::path::{Path, PathBuf}; +use std::str::FromStr; + +macro_rules! update_and_test { + ($self: ident, $set_func:ident, $value:expr, $get_func:ident) => { + if let Some(v) = $value { + $self.$set_func(v)?; + if $self.$get_func()? != v { + return Err(Error::new(Other)); + } + } + }; +} + +macro_rules! update { + ($self: ident, $set_func:ident, $value:expr) => { + if let Some(v) = $value { + let _ = $self.$set_func(v); + } + }; +} + +pub mod blkio; +pub mod cgroup; +pub mod cgroup_builder; +pub mod cpu; +pub mod cpuacct; +pub mod cpuset; +pub mod devices; +pub mod error; +pub mod events; +pub mod freezer; +pub mod hierarchies; +pub mod hugetlb; +pub mod memory; +pub mod net_cls; +pub mod net_prio; +pub mod perf_event; +pub mod pid; +pub mod rdma; +pub mod systemd; + +use crate::fs::blkio::BlkIoController; +use crate::fs::cpu::CpuController; +use crate::fs::cpuacct::CpuAcctController; +use crate::fs::cpuset::CpuSetController; +use crate::fs::devices::DevicesController; +use crate::fs::error::ErrorKind::*; +use crate::fs::error::*; +use crate::fs::freezer::FreezerController; +use crate::fs::hugetlb::HugeTlbController; +use crate::fs::memory::MemController; +use crate::fs::net_cls::NetClsController; +use crate::fs::net_prio::NetPrioController; +use crate::fs::perf_event::PerfEventController; +use crate::fs::pid::PidController; +use crate::fs::rdma::RdmaController; +use crate::fs::systemd::SystemdController; + +#[doc(inline)] +pub use crate::fs::cgroup::Cgroup; + +/// Contains all the subsystems that are available in this crate. +#[derive(Debug, Clone)] +pub enum Subsystem { + /// Controller for the `Pid` subsystem, see `PidController` for more information. + Pid(PidController), + /// Controller for the `Mem` subsystem, see `MemController` for more information. + Mem(MemController), + /// Controller for the `CpuSet subsystem, see `CpuSetController` for more information. + CpuSet(CpuSetController), + /// Controller for the `CpuAcct` subsystem, see `CpuAcctController` for more information. + CpuAcct(CpuAcctController), + /// Controller for the `Cpu` subsystem, see `CpuController` for more information. + Cpu(CpuController), + /// Controller for the `Devices` subsystem, see `DevicesController` for more information. + Devices(DevicesController), + /// Controller for the `Freezer` subsystem, see `FreezerController` for more information. + Freezer(FreezerController), + /// Controller for the `NetCls` subsystem, see `NetClsController` for more information. + NetCls(NetClsController), + /// Controller for the `BlkIo` subsystem, see `BlkIoController` for more information. + BlkIo(BlkIoController), + /// Controller for the `PerfEvent` subsystem, see `PerfEventController` for more information. + PerfEvent(PerfEventController), + /// Controller for the `NetPrio` subsystem, see `NetPrioController` for more information. + NetPrio(NetPrioController), + /// Controller for the `HugeTlb` subsystem, see `HugeTlbController` for more information. + HugeTlb(HugeTlbController), + /// Controller for the `Rdma` subsystem, see `RdmaController` for more information. + Rdma(RdmaController), + /// Controller for the `Systemd` subsystem, see `SystemdController` for more information. + Systemd(SystemdController), +} + +#[doc(hidden)] +#[derive(Eq, PartialEq, Debug, Clone)] +pub enum Controllers { + Pids, + Mem, + CpuSet, + CpuAcct, + Cpu, + Devices, + Freezer, + NetCls, + BlkIo, + PerfEvent, + NetPrio, + HugeTlb, + Rdma, + Systemd, +} + +impl fmt::Display for Controllers { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Controllers::Pids => write!(f, "pids"), + Controllers::Mem => write!(f, "memory"), + Controllers::CpuSet => write!(f, "cpuset"), + Controllers::CpuAcct => write!(f, "cpuacct"), + Controllers::Cpu => write!(f, "cpu"), + Controllers::Devices => write!(f, "devices"), + Controllers::Freezer => write!(f, "freezer"), + Controllers::NetCls => write!(f, "net_cls"), + Controllers::BlkIo => write!(f, "blkio"), + Controllers::PerfEvent => write!(f, "perf_event"), + Controllers::NetPrio => write!(f, "net_prio"), + Controllers::HugeTlb => write!(f, "hugetlb"), + Controllers::Rdma => write!(f, "rdma"), + Controllers::Systemd => write!(f, "name=systemd"), + } + } +} + +mod sealed { + use super::*; + + pub trait ControllerInternal { + fn apply(&self, res: &Resources) -> Result<()>; + + // meta stuff + fn control_type(&self) -> Controllers; + fn get_path(&self) -> &PathBuf; + fn get_path_mut(&mut self) -> &mut PathBuf; + fn get_base(&self) -> &PathBuf; + + /// Hooks running after controller crated, if have + fn post_create(&self) {} + + fn is_v2(&self) -> bool { + false + } + + fn verify_path(&self) -> Result<()> { + if self.get_path().starts_with(self.get_base()) { + Ok(()) + } else { + Err(Error::new(ErrorKind::InvalidPath)) + } + } + + fn open_path(&self, p: &str, w: bool) -> Result { + let mut path = self.get_path().clone(); + path.push(p); + + self.verify_path()?; + + if w { + match File::create(&path) { + Err(e) => Err(Error::with_cause( + ErrorKind::WriteFailed( + path.display().to_string(), + "[CREATE FILE]".to_string(), + ), + e, + )), + Ok(file) => Ok(file), + } + } else { + match File::open(&path) { + Err(e) => Err(Error::with_cause( + ErrorKind::ReadFailed(path.display().to_string()), + e, + )), + Ok(file) => Ok(file), + } + } + } + + fn get_max_value(&self, f: &str) -> Result { + self.open_path(f, false).and_then(|mut file| { + let mut string = String::new(); + let res = file.read_to_string(&mut string); + match res { + Ok(_) => parse_max_value(&string), + Err(e) => Err(Error::with_cause(ReadFailed(f.to_string()), e)), + } + }) + } + + #[doc(hidden)] + fn path_exists(&self, p: &str) -> bool { + if self.verify_path().is_err() { + return false; + } + + std::path::Path::new(p).exists() + } + } + + pub trait CustomizedAttribute: ControllerInternal { + fn set(&self, key: &str, value: &str) -> Result<()> { + self.open_path(key, true).and_then(|mut file| { + file.write_all(value.as_ref()).map_err(|e| { + Error::with_cause(WriteFailed(key.to_string(), value.to_string()), e) + }) + }) + } + + #[allow(dead_code)] + fn get(&self, key: &str) -> Result { + self.open_path(key, false).and_then(|mut file: File| { + let mut string = String::new(); + match file.read_to_string(&mut string) { + Ok(_) => Ok(string.trim().to_owned()), + Err(e) => Err(Error::with_cause(ReadFailed(key.to_string()), e)), + } + }) + } + } +} + +pub(crate) use crate::fs::sealed::{ControllerInternal, CustomizedAttribute}; + +/// A Controller is a subsystem attached to the control group. +/// +/// Implementors are able to control certain aspects of a control group. +pub trait Controller { + #[doc(hidden)] + fn control_type(&self) -> Controllers; + + /// The file system path to the controller. + fn path(&self) -> &Path; + + /// Root path of the file system to the controller. + fn base(&self) -> &Path; + + /// Apply a set of resources to the Controller, invoking its internal functions to pass the + /// kernel the information. + fn apply(&self, res: &Resources) -> Result<()>; + + /// Create this controller + fn create(&self); + + /// Does this controller already exist? + fn exists(&self) -> bool; + + /// Set notify_on_release + fn set_notify_on_release(&self, enable: bool) -> Result<()>; + + /// Set release_agent + fn set_release_agent(&self, path: &str) -> Result<()>; + + /// Delete the controller. + fn delete(&self) -> Result<()>; + + /// Attach a task to this controller. + fn add_task(&self, pid: &CgroupPid) -> Result<()>; + + /// Attach a task to this controller. + fn add_task_by_tgid(&self, pid: &CgroupPid) -> Result<()>; + + /// set cgroup type. + fn set_cgroup_type(&self, cgroup_type: &str) -> Result<()>; + + /// get cgroup type. + fn get_cgroup_type(&self) -> Result; + + /// Get the list of tasks that this controller has. + fn tasks(&self) -> Vec; + + /// Get the list of procs that this controller has. + fn procs(&self) -> Vec; + + fn v2(&self) -> bool; +} + +impl Controller for T +where + T: ControllerInternal, +{ + fn control_type(&self) -> Controllers { + ControllerInternal::control_type(self) + } + + fn path(&self) -> &Path { + self.get_path() + } + + fn base(&self) -> &Path { + self.get_base() + } + + /// Apply a set of resources to the Controller, invoking its internal functions to pass the + /// kernel the information. + fn apply(&self, res: &Resources) -> Result<()> { + ControllerInternal::apply(self, res) + } + + /// Create this controller + fn create(&self) { + self.verify_path() + .unwrap_or_else(|_| panic!("path should be valid: {:?}", self.path())); + + match ::std::fs::create_dir_all(self.get_path()) { + Ok(_) => self.post_create(), + Err(e) => warn!("error create_dir: {:?} error: {:?}", self.get_path(), e), + } + } + + /// Set notify_on_release + fn set_notify_on_release(&self, enable: bool) -> Result<()> { + if self.is_v2() { + return Err(Error::new(ErrorKind::CgroupVersion)); + } + self.open_path("notify_on_release", true) + .and_then(|mut file| { + write!(file, "{}", enable as i32).map_err(|e| { + Error::with_cause( + ErrorKind::WriteFailed("notify_on_release".to_string(), enable.to_string()), + e, + ) + }) + }) + } + + /// Set release_agent + fn set_release_agent(&self, path: &str) -> Result<()> { + if self.is_v2() { + return Err(Error::new(ErrorKind::CgroupVersion)); + } + self.open_path("release_agent", true).and_then(|mut file| { + file.write_all(path.as_bytes()).map_err(|e| { + Error::with_cause( + ErrorKind::WriteFailed("release_agent".to_string(), path.to_string()), + e, + ) + }) + }) + } + /// Does this controller already exist? + fn exists(&self) -> bool { + self.get_path().exists() + } + + /// Delete the controller. + fn delete(&self) -> Result<()> { + if !self.get_path().exists() { + return Ok(()); + } + + // Compatible with runC for remove dir operation + // https://github.com/opencontainers/runc/blob/main/libcontainer/cgroups/utils.go#L272 + // + // We trying to remove all paths five times with increasing delay between tries. + // If after all there are not removed cgroups - appropriate error will be + // returned. + let mut delay = std::time::Duration::from_millis(10); + let cgroup_path = self.get_path(); + for _i in 0..4 { + if let Ok(()) = remove_dir(cgroup_path) { + return Ok(()); + } + std::thread::sleep(delay); + delay *= 2; + } + + remove_dir(cgroup_path) + } + + /// Attach a task to this controller. + fn add_task(&self, pid: &CgroupPid) -> Result<()> { + let mut file_name = "tasks"; + if self.is_v2() { + file_name = "cgroup.threads"; + } + self.open_path(file_name, true).and_then(|mut file| { + file.write_all(pid.pid.to_string().as_ref()).map_err(|e| { + Error::with_cause( + ErrorKind::WriteFailed(file_name.to_string(), pid.pid.to_string()), + e, + ) + }) + }) + } + + /// Attach a task to this controller by thread group id. + fn add_task_by_tgid(&self, pid: &CgroupPid) -> Result<()> { + let file_name = "cgroup.procs"; + self.open_path(file_name, true).and_then(|mut file| { + file.write_all(pid.pid.to_string().as_ref()).map_err(|e| { + Error::with_cause( + ErrorKind::WriteFailed(file_name.to_string(), pid.pid.to_string()), + e, + ) + }) + }) + } + + /// Get the list of procs that this controller has. + fn procs(&self) -> Vec { + let file_name = "cgroup.procs"; + self.open_path(file_name, false) + .map(|file| { + let bf = BufReader::new(file); + let mut v = Vec::new(); + for line in bf.lines() { + match line { + Ok(line) => { + let n = line.trim().parse().unwrap_or(0u64); + v.push(n); + } + Err(_) => break, + } + } + v.into_iter().map(CgroupPid::from).collect() + }) + .unwrap_or_default() + } + + /// Get the list of tasks that this controller has. + fn tasks(&self) -> Vec { + let mut file_name = "tasks"; + if self.is_v2() { + file_name = "cgroup.threads"; + } + self.open_path(file_name, false) + .map(|file| { + let bf = BufReader::new(file); + let mut v = Vec::new(); + for line in bf.lines() { + match line { + Ok(line) => { + let n = line.trim().parse().unwrap_or(0u64); + v.push(n); + } + Err(_) => break, + } + } + v.into_iter().map(CgroupPid::from).collect() + }) + .unwrap_or_default() + } + + /// set cgroup.type + fn set_cgroup_type(&self, cgroup_type: &str) -> Result<()> { + if !self.is_v2() { + return Err(Error::new(ErrorKind::CgroupVersion)); + } + let file_name = "cgroup.type"; + self.open_path(file_name, true).and_then(|mut file| { + file.write_all(cgroup_type.as_bytes()).map_err(|e| { + Error::with_cause( + ErrorKind::WriteFailed(file_name.to_string(), cgroup_type.to_string()), + e, + ) + }) + }) + } + + /// get cgroup.type + fn get_cgroup_type(&self) -> Result { + if !self.is_v2() { + return Err(Error::new(ErrorKind::CgroupVersion)); + } + let file_name = "cgroup.type"; + self.open_path(file_name, false).and_then(|mut file: File| { + let mut string = String::new(); + match file.read_to_string(&mut string) { + Ok(_) => Ok(string.trim().to_owned()), + Err(e) => Err(Error::with_cause( + ErrorKind::ReadFailed(file_name.to_string()), + e, + )), + } + }) + } + + fn v2(&self) -> bool { + self.is_v2() + } +} + +// remove_dir aims to remove cgroup path. It does so recursively, +// by removing any subdirectories (sub-cgroups) first. +fn remove_dir(dir: &Path) -> Result<()> { + // try the fast path first. + if fs::remove_dir(dir).is_ok() { + return Ok(()); + } + + if dir.exists() && dir.is_dir() { + for entry in fs::read_dir(dir) + .map_err(|e| Error::with_cause(ReadFailed(dir.display().to_string()), e))? + { + let entry = + entry.map_err(|e| Error::with_cause(ReadFailed(dir.display().to_string()), e))?; + let path = entry.path(); + if path.is_dir() { + remove_dir(&path)?; + } + } + fs::remove_dir(dir).map_err(|e| Error::with_cause(RemoveFailed, e))?; + } + + Ok(()) +} + +#[doc(hidden)] +pub trait ControllIdentifier { + fn controller_type() -> Controllers; +} + +/// Control group hierarchy (right now, only V1 is supported, but in the future Unified will be +/// implemented as well). +pub trait Hierarchy: std::fmt::Debug + Send + Sync { + /// Returns what subsystems are supported by the hierarchy. + fn subsystems(&self) -> Vec; + + /// Returns the root directory of the hierarchy. + fn root(&self) -> PathBuf; + + /// Return a handle to the root control group in the hierarchy. + fn root_control_group(&self) -> Cgroup; + + /// Return a handle to the parent control group in the hierarchy. + fn parent_control_group(&self, path: &str) -> Cgroup; + + fn v2(&self) -> bool; +} + +/// Resource limits for the memory subsystem. +#[derive(Debug, Clone, Eq, PartialEq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct MemoryResources { + /// How much memory (in bytes) can the kernel consume. + pub kernel_memory_limit: Option, + /// Upper limit of memory usage of the control group's tasks. + pub memory_hard_limit: Option, + /// How much memory the tasks in the control group can use when the system is under memory + /// pressure. + pub memory_soft_limit: Option, + /// How much of the kernel's memory (in bytes) can be used for TCP-related buffers. + pub kernel_tcp_memory_limit: Option, + /// How much memory and swap together can the tasks in the control group use. + pub memory_swap_limit: Option, + /// Controls the tendency of the kernel to swap out parts of the address space of the tasks to + /// disk. Lower value implies less likely. + /// + /// Note, however, that a value of zero does not mean the process is never swapped out. Use the + /// traditional `mlock(2)` system call for that purpose. + pub swappiness: Option, + /// Customized key-value attributes + /// + /// # Usage: + /// ``` + /// let resource = &mut cgroups_rs::fs::Resources::default(); + /// resource.memory.attrs.insert("memory.numa_balancing".to_string(), "true".to_string()); + /// // apply here + /// ``` + pub attrs: HashMap, +} + +/// Resources limits on the number of processes. +#[derive(Debug, Clone, Eq, PartialEq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct PidResources { + /// The maximum number of processes that can exist in the control group. + /// + /// Note that attaching processes to the control group will still succeed _even_ if the limit + /// would be violated, however forks/clones inside the control group will have with `EAGAIN` if + /// they would violate the limit set here. + pub maximum_number_of_processes: Option, +} + +/// Resources limits about how the tasks can use the CPU. +#[derive(Debug, Clone, Eq, PartialEq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CpuResources { + // cpuset + /// A comma-separated list of CPU IDs where the task in the control group can run. Dashes + /// between numbers indicate ranges. + pub cpus: Option, + /// Same syntax as the `cpus` field of this structure, but applies to memory nodes instead of + /// processors. + pub mems: Option, + // cpu + /// Weight of how much of the total CPU time should this control group get. Note that this is + /// hierarchical, so this is weighted against the siblings of this control group. + pub shares: Option, + /// In one `period`, how much can the tasks run in microseconds. + pub quota: Option, + /// Period of time in microseconds. + pub period: Option, + /// This is currently a no-operation. + pub realtime_runtime: Option, + /// This is currently a no-operation. + pub realtime_period: Option, + /// Customized key-value attributes + /// # Usage: + /// ``` + /// let resource = &mut cgroups_rs::fs::Resources::default(); + /// resource.cpu.attrs.insert("cpu.cfs_init_buffer_us".to_string(), "10".to_string()); + /// // apply here + /// ``` + pub attrs: HashMap, +} + +/// A device resource that can be allowed or denied access to. +#[derive(Debug, Clone, Eq, PartialEq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DeviceResource { + /// If true, access to the device is allowed, otherwise it's denied. + pub allow: bool, + /// `'c'` for character device, `'b'` for block device; or `'a'` for all devices. + pub devtype: crate::fs::devices::DeviceType, + /// The major number of the device. + pub major: i64, + /// The minor number of the device. + pub minor: i64, + /// Sequence of `'r'`, `'w'` or `'m'`, each denoting read, write or mknod permissions. + pub access: Vec, +} + +/// Limit the usage of devices for the control group's tasks. +#[derive(Debug, Clone, Eq, PartialEq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DeviceResources { + /// For each device in the list, the limits in the structure are applied. + pub devices: Vec, +} + +/// Assigned priority for a network device. +#[derive(Debug, Clone, Eq, PartialEq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct NetworkPriority { + /// The name (as visible in `ifconfig`) of the interface. + pub name: String, + /// Assigned priority. + pub priority: u64, +} + +/// Collections of limits and tags that can be imposed on packets emitted by the tasks in the +/// control group. +#[derive(Debug, Clone, Eq, PartialEq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct NetworkResources { + /// The networking class identifier to attach to the packets. + /// + /// This can then later be used in iptables and such to have special rules. + pub class_id: Option, + /// Priority of the egress traffic for each interface. + pub priorities: Vec, +} + +/// A hugepage type and its consumption limit for the control group. +#[derive(Debug, Clone, Eq, PartialEq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct HugePageResource { + /// The size of the hugepage, i.e. `2MB`, `1GB`, etc. + pub size: String, + /// The amount of bytes (of memory consumed by the tasks) that are allowed to be backed by + /// hugepages. + pub limit: u64, +} + +/// Provides the ability to set consumption limit on each type of hugepages. +#[derive(Debug, Clone, Eq, PartialEq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct HugePageResources { + /// Set a limit of consumption for each hugepages type. + pub limits: Vec, +} + +/// Weight for a particular block device. +#[derive(Debug, Clone, Eq, PartialEq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct BlkIoDeviceResource { + /// The major number of the device. + pub major: u64, + /// The minor number of the device. + pub minor: u64, + /// The weight of the device against the descendant nodes. + pub weight: Option, + /// The weight of the device against the sibling nodes. + pub leaf_weight: Option, +} + +/// Provides the ability to throttle a device (both byte/sec, and IO op/s) +#[derive(Debug, Clone, Eq, PartialEq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct BlkIoDeviceThrottleResource { + /// The major number of the device. + pub major: u64, + /// The minor number of the device. + pub minor: u64, + /// The rate. + pub rate: u64, +} + +/// General block I/O resource limits. +#[derive(Debug, Clone, Eq, PartialEq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct BlkIoResources { + /// The weight of the control group against descendant nodes. + pub weight: Option, + /// The weight of the control group against sibling nodes. + pub leaf_weight: Option, + /// For each device, a separate weight (both normal and leaf) can be provided. + pub weight_device: Vec, + /// Throttled read bytes/second can be provided for each device. + pub throttle_read_bps_device: Vec, + /// Throttled read IO operations per second can be provided for each device. + pub throttle_read_iops_device: Vec, + /// Throttled written bytes/second can be provided for each device. + pub throttle_write_bps_device: Vec, + /// Throttled write IO operations per second can be provided for each device. + pub throttle_write_iops_device: Vec, + + /// Customized key-value attributes + /// # Usage: + /// ``` + /// let resource = &mut cgroups_rs::fs::Resources::default(); + /// resource.blkio.attrs.insert("io.cost.weight".to_string(), "10".to_string()); + /// // apply here + /// ``` + pub attrs: HashMap, +} + +/// The resource limits and constraints that will be set on the control group. +#[derive(Debug, Clone, Eq, PartialEq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Resources { + /// Memory usage related limits. + pub memory: MemoryResources, + /// Process identifier related limits. + pub pid: PidResources, + /// CPU related limits. + pub cpu: CpuResources, + /// Device related limits. + pub devices: DeviceResources, + /// Network related tags and limits. + pub network: NetworkResources, + /// Hugepages consumption related limits. + pub hugepages: HugePageResources, + /// Block device I/O related limits. + pub blkio: BlkIoResources, +} + +/// A structure representing a `pid`. Currently implementations exist for `u64` and +/// `std::process::Child`. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct CgroupPid { + /// The process identifier + pub pid: u64, +} + +impl From for CgroupPid { + fn from(u: u64) -> CgroupPid { + CgroupPid { pid: u } + } +} + +impl From<&std::process::Child> for CgroupPid { + fn from(u: &std::process::Child) -> CgroupPid { + CgroupPid { pid: u.id() as u64 } + } +} + +impl Subsystem { + fn enter(self, path: &Path) -> Self { + match self { + Subsystem::Pid(mut cont) => Subsystem::Pid({ + cont.get_path_mut().push(path); + cont + }), + Subsystem::Mem(mut cont) => Subsystem::Mem({ + cont.get_path_mut().push(path); + cont + }), + Subsystem::CpuSet(mut cont) => Subsystem::CpuSet({ + cont.get_path_mut().push(path); + cont + }), + Subsystem::CpuAcct(mut cont) => Subsystem::CpuAcct({ + cont.get_path_mut().push(path); + cont + }), + Subsystem::Cpu(mut cont) => Subsystem::Cpu({ + cont.get_path_mut().push(path); + cont + }), + Subsystem::Devices(mut cont) => Subsystem::Devices({ + cont.get_path_mut().push(path); + cont + }), + Subsystem::Freezer(mut cont) => Subsystem::Freezer({ + cont.get_path_mut().push(path); + cont + }), + Subsystem::NetCls(mut cont) => Subsystem::NetCls({ + cont.get_path_mut().push(path); + cont + }), + Subsystem::BlkIo(mut cont) => Subsystem::BlkIo({ + cont.get_path_mut().push(path); + cont + }), + Subsystem::PerfEvent(mut cont) => Subsystem::PerfEvent({ + cont.get_path_mut().push(path); + cont + }), + Subsystem::NetPrio(mut cont) => Subsystem::NetPrio({ + cont.get_path_mut().push(path); + cont + }), + Subsystem::HugeTlb(mut cont) => Subsystem::HugeTlb({ + cont.get_path_mut().push(path); + cont + }), + Subsystem::Rdma(mut cont) => Subsystem::Rdma({ + cont.get_path_mut().push(path); + cont + }), + Subsystem::Systemd(mut cont) => Subsystem::Systemd({ + cont.get_path_mut().push(path); + cont + }), + } + } + + pub fn to_controller(&self) -> &dyn Controller { + match self { + Subsystem::Pid(cont) => cont, + Subsystem::Mem(cont) => cont, + Subsystem::CpuSet(cont) => cont, + Subsystem::CpuAcct(cont) => cont, + Subsystem::Cpu(cont) => cont, + Subsystem::Devices(cont) => cont, + Subsystem::Freezer(cont) => cont, + Subsystem::NetCls(cont) => cont, + Subsystem::BlkIo(cont) => cont, + Subsystem::PerfEvent(cont) => cont, + Subsystem::NetPrio(cont) => cont, + Subsystem::HugeTlb(cont) => cont, + Subsystem::Rdma(cont) => cont, + Subsystem::Systemd(cont) => cont, + } + } + + pub fn controller_name(&self) -> String { + self.to_controller().control_type().to_string() + } +} + +/// The values for `memory.hight` or `pids.max` +#[derive(Eq, PartialEq, Copy, Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum MaxValue { + /// This value is returned when the text is `"max"`. + Max, + /// When the value is a numerical value, they are returned via this enum field. + Value(i64), +} + +#[allow(clippy::derivable_impls)] +impl Default for MaxValue { + fn default() -> Self { + MaxValue::Max + } +} + +impl MaxValue { + #[allow(clippy::should_implement_trait, clippy::wrong_self_convention)] + fn to_i64(&self) -> i64 { + match self { + MaxValue::Max => -1, + MaxValue::Value(num) => *num, + } + } +} + +impl fmt::Display for MaxValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + MaxValue::Max => write!(f, "max"), + MaxValue::Value(num) => write!(f, "{}", num), + } + } +} + +pub fn parse_max_value(s: &str) -> Result { + if s.trim() == "max" { + return Ok(MaxValue::Max); + } + match s.trim().parse() { + Ok(val) => Ok(MaxValue::Value(val)), + Err(e) => Err(Error::with_cause(ParseError, e)), + } +} + +// Flat keyed +// KEY0 VAL0\n +// KEY1 VAL1\n +pub fn flat_keyed_to_vec(mut file: File) -> Result> { + let mut content = String::new(); + file.read_to_string(&mut content) + .map_err(|e| Error::with_cause(ReadFailed("FIXME: read_string_from".to_string()), e))?; + + let mut v = Vec::new(); + for line in content.lines() { + let parts: Vec<&str> = line.split(' ').collect(); + if parts.len() == 2 { + if let Ok(i) = parts[1].parse::() { + v.push((parts[0].to_string(), i)); + } + } + } + Ok(v) +} + +// Flat keyed +// KEY0 VAL0\n +// KEY1 VAL1\n +pub fn flat_keyed_to_hashmap(mut file: File) -> Result> { + let mut content = String::new(); + file.read_to_string(&mut content) + .map_err(|e| Error::with_cause(ReadFailed("FIXME: read_string_from".to_string()), e))?; + + let mut h = HashMap::new(); + for line in content.lines() { + let parts: Vec<&str> = line.split(' ').collect(); + if parts.len() == 2 { + if let Ok(i) = parts[1].parse::() { + h.insert(parts[0].to_string(), i); + } + } + } + Ok(h) +} + +// Nested keyed +// KEY0 SUB_KEY0=VAL00 SUB_KEY1=VAL01... +// KEY1 SUB_KEY0=VAL10 SUB_KEY1=VAL11... +pub fn nested_keyed_to_hashmap(mut file: File) -> Result>> { + let mut content = String::new(); + file.read_to_string(&mut content) + .map_err(|e| Error::with_cause(ReadFailed("FIXME: read_string_from".to_string()), e))?; + + let mut h = HashMap::new(); + for line in content.lines() { + let parts: Vec<&str> = line.split(' ').collect(); + if parts.is_empty() { + continue; + } + let mut th = HashMap::new(); + for item in parts[1..].iter() { + let fields: Vec<&str> = item.split('=').collect(); + if fields.len() == 2 { + if let Ok(i) = fields[1].parse::() { + th.insert(fields[0].to_string(), i); + } + } + } + h.insert(parts[0].to_string(), th); + } + + Ok(h) +} + +fn read_from(mut file: File) -> Result +where + T: FromStr, + ::Err: 'static + Send + Sync + std::error::Error, +{ + let mut string = String::new(); + match file.read_to_string(&mut string) { + Ok(_) => string + .trim() + .parse::() + .map_err(|e| Error::with_cause(ParseError, e)), + Err(e) => Err(Error::with_cause( + ReadFailed("FIXME: can't get path in fn read_from".to_string()), + e, + )), + } +} + +fn read_string_from(mut file: File) -> Result { + let mut string = String::new(); + match file.read_to_string(&mut string) { + Ok(_) => Ok(string.trim().to_string()), + Err(e) => Err(Error::with_cause( + ReadFailed("FIXME: can't get path in fn read_string_from".to_string()), + e, + )), + } +} + +/// read and parse an u64 data +fn read_u64_from(file: File) -> Result { + read_from::(file) +} + +/// read and parse an i64 data +fn read_i64_from(file: File) -> Result { + read_from::(file) +} diff --git a/src/net_cls.rs b/src/fs/net_cls.rs similarity index 96% rename from src/net_cls.rs rename to src/fs/net_cls.rs index a98ab8c..d87f8ee 100644 --- a/src/net_cls.rs +++ b/src/fs/net_cls.rs @@ -10,11 +10,11 @@ use std::io::Write; use std::path::PathBuf; -use crate::error::ErrorKind::*; -use crate::error::*; +use crate::fs::error::ErrorKind::*; +use crate::fs::error::*; -use crate::read_u64_from; -use crate::{ +use crate::fs::read_u64_from; +use crate::fs::{ ControllIdentifier, ControllerInternal, Controllers, NetworkResources, Resources, Subsystem, }; diff --git a/src/net_prio.rs b/src/fs/net_prio.rs similarity index 97% rename from src/net_prio.rs rename to src/fs/net_prio.rs index 51fede4..199866c 100644 --- a/src/net_prio.rs +++ b/src/fs/net_prio.rs @@ -11,11 +11,11 @@ use std::collections::HashMap; use std::io::{BufRead, BufReader, Write}; use std::path::PathBuf; -use crate::error::ErrorKind::*; -use crate::error::*; +use crate::fs::error::ErrorKind::*; +use crate::fs::error::*; -use crate::read_u64_from; -use crate::{ +use crate::fs::read_u64_from; +use crate::fs::{ ControllIdentifier, ControllerInternal, Controllers, NetworkResources, Resources, Subsystem, }; diff --git a/src/perf_event.rs b/src/fs/perf_event.rs similarity index 94% rename from src/perf_event.rs rename to src/fs/perf_event.rs index 002cff6..ea6c54a 100644 --- a/src/perf_event.rs +++ b/src/fs/perf_event.rs @@ -9,9 +9,9 @@ //! [tools/perf/Documentation/perf-record.txt](https://raw.githubusercontent.com/torvalds/linux/master/tools/perf/Documentation/perf-record.txt) use std::path::PathBuf; -use crate::error::*; +use crate::fs::error::*; -use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; +use crate::fs::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; /// A controller that allows controlling the `perf_event` subsystem of a Cgroup. /// diff --git a/src/pid.rs b/src/fs/pid.rs similarity index 97% rename from src/pid.rs rename to src/fs/pid.rs index f98f4db..1cfbc81 100644 --- a/src/pid.rs +++ b/src/fs/pid.rs @@ -11,11 +11,11 @@ use std::io::{Read, Write}; use std::path::PathBuf; -use crate::error::ErrorKind::*; -use crate::error::*; +use crate::fs::error::ErrorKind::*; +use crate::fs::error::*; -use crate::read_u64_from; -use crate::{ +use crate::fs::read_u64_from; +use crate::fs::{ parse_max_value, ControllIdentifier, ControllerInternal, Controllers, MaxValue, PidResources, Resources, Subsystem, }; diff --git a/src/rdma.rs b/src/fs/rdma.rs similarity index 93% rename from src/rdma.rs rename to src/fs/rdma.rs index 5d76fb1..910191c 100644 --- a/src/rdma.rs +++ b/src/fs/rdma.rs @@ -10,11 +10,11 @@ use std::io::Write; use std::path::PathBuf; -use crate::error::ErrorKind::*; -use crate::error::*; +use crate::fs::error::ErrorKind::*; +use crate::fs::error::*; -use crate::read_string_from; -use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; +use crate::fs::read_string_from; +use crate::fs::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; /// A controller that allows controlling the `rdma` subsystem of a Cgroup. /// diff --git a/src/systemd.rs b/src/fs/systemd.rs similarity index 93% rename from src/systemd.rs rename to src/fs/systemd.rs index 6002c1c..7f5ba8f 100644 --- a/src/systemd.rs +++ b/src/fs/systemd.rs @@ -7,9 +7,9 @@ //! use std::path::PathBuf; -use crate::error::*; +use crate::fs::error::*; -use crate::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; +use crate::fs::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; /// A controller that allows controlling the `systemd` subsystem of a Cgroup. /// diff --git a/src/lib.rs b/src/lib.rs index 54e940b..2528ecf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,1032 +1,7 @@ // Copyright (c) 2018 Levente Kurusa -// Copyright (c) 2020 Ant Group +// Copyright (c) 2020-2025 Ant Group // // SPDX-License-Identifier: Apache-2.0 or MIT // -#![allow(clippy::unnecessary_unwrap)] -use log::*; - -use std::collections::HashMap; -use std::fmt; -use std::fs::{self, File}; -use std::io::{BufRead, BufReader, Read, Write}; -use std::path::{Path, PathBuf}; -use std::str::FromStr; - -macro_rules! update_and_test { - ($self: ident, $set_func:ident, $value:expr, $get_func:ident) => { - if let Some(v) = $value { - $self.$set_func(v)?; - if $self.$get_func()? != v { - return Err(Error::new(Other)); - } - } - }; -} - -macro_rules! update { - ($self: ident, $set_func:ident, $value:expr) => { - if let Some(v) = $value { - let _ = $self.$set_func(v); - } - }; -} - -pub mod blkio; -pub mod cgroup; -pub mod cgroup_builder; -pub mod cpu; -pub mod cpuacct; -pub mod cpuset; -pub mod devices; -pub mod error; -pub mod events; -pub mod freezer; -pub mod hierarchies; -pub mod hugetlb; -pub mod memory; -pub mod net_cls; -pub mod net_prio; -pub mod perf_event; -pub mod pid; -pub mod rdma; -pub mod systemd; - -use crate::blkio::BlkIoController; -use crate::cpu::CpuController; -use crate::cpuacct::CpuAcctController; -use crate::cpuset::CpuSetController; -use crate::devices::DevicesController; -use crate::error::ErrorKind::*; -use crate::error::*; -use crate::freezer::FreezerController; -use crate::hugetlb::HugeTlbController; -use crate::memory::MemController; -use crate::net_cls::NetClsController; -use crate::net_prio::NetPrioController; -use crate::perf_event::PerfEventController; -use crate::pid::PidController; -use crate::rdma::RdmaController; -use crate::systemd::SystemdController; - -#[doc(inline)] -pub use crate::cgroup::Cgroup; - -/// Contains all the subsystems that are available in this crate. -#[derive(Debug, Clone)] -pub enum Subsystem { - /// Controller for the `Pid` subsystem, see `PidController` for more information. - Pid(PidController), - /// Controller for the `Mem` subsystem, see `MemController` for more information. - Mem(MemController), - /// Controller for the `CpuSet subsystem, see `CpuSetController` for more information. - CpuSet(CpuSetController), - /// Controller for the `CpuAcct` subsystem, see `CpuAcctController` for more information. - CpuAcct(CpuAcctController), - /// Controller for the `Cpu` subsystem, see `CpuController` for more information. - Cpu(CpuController), - /// Controller for the `Devices` subsystem, see `DevicesController` for more information. - Devices(DevicesController), - /// Controller for the `Freezer` subsystem, see `FreezerController` for more information. - Freezer(FreezerController), - /// Controller for the `NetCls` subsystem, see `NetClsController` for more information. - NetCls(NetClsController), - /// Controller for the `BlkIo` subsystem, see `BlkIoController` for more information. - BlkIo(BlkIoController), - /// Controller for the `PerfEvent` subsystem, see `PerfEventController` for more information. - PerfEvent(PerfEventController), - /// Controller for the `NetPrio` subsystem, see `NetPrioController` for more information. - NetPrio(NetPrioController), - /// Controller for the `HugeTlb` subsystem, see `HugeTlbController` for more information. - HugeTlb(HugeTlbController), - /// Controller for the `Rdma` subsystem, see `RdmaController` for more information. - Rdma(RdmaController), - /// Controller for the `Systemd` subsystem, see `SystemdController` for more information. - Systemd(SystemdController), -} - -#[doc(hidden)] -#[derive(Eq, PartialEq, Debug, Clone)] -pub enum Controllers { - Pids, - Mem, - CpuSet, - CpuAcct, - Cpu, - Devices, - Freezer, - NetCls, - BlkIo, - PerfEvent, - NetPrio, - HugeTlb, - Rdma, - Systemd, -} - -impl fmt::Display for Controllers { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Controllers::Pids => write!(f, "pids"), - Controllers::Mem => write!(f, "memory"), - Controllers::CpuSet => write!(f, "cpuset"), - Controllers::CpuAcct => write!(f, "cpuacct"), - Controllers::Cpu => write!(f, "cpu"), - Controllers::Devices => write!(f, "devices"), - Controllers::Freezer => write!(f, "freezer"), - Controllers::NetCls => write!(f, "net_cls"), - Controllers::BlkIo => write!(f, "blkio"), - Controllers::PerfEvent => write!(f, "perf_event"), - Controllers::NetPrio => write!(f, "net_prio"), - Controllers::HugeTlb => write!(f, "hugetlb"), - Controllers::Rdma => write!(f, "rdma"), - Controllers::Systemd => write!(f, "name=systemd"), - } - } -} - -mod sealed { - use super::*; - - pub trait ControllerInternal { - fn apply(&self, res: &Resources) -> Result<()>; - - // meta stuff - fn control_type(&self) -> Controllers; - fn get_path(&self) -> &PathBuf; - fn get_path_mut(&mut self) -> &mut PathBuf; - fn get_base(&self) -> &PathBuf; - - /// Hooks running after controller crated, if have - fn post_create(&self) {} - - fn is_v2(&self) -> bool { - false - } - - fn verify_path(&self) -> Result<()> { - if self.get_path().starts_with(self.get_base()) { - Ok(()) - } else { - Err(Error::new(ErrorKind::InvalidPath)) - } - } - - fn open_path(&self, p: &str, w: bool) -> Result { - let mut path = self.get_path().clone(); - path.push(p); - - self.verify_path()?; - - if w { - match File::create(&path) { - Err(e) => Err(Error::with_cause( - ErrorKind::WriteFailed( - path.display().to_string(), - "[CREATE FILE]".to_string(), - ), - e, - )), - Ok(file) => Ok(file), - } - } else { - match File::open(&path) { - Err(e) => Err(Error::with_cause( - ErrorKind::ReadFailed(path.display().to_string()), - e, - )), - Ok(file) => Ok(file), - } - } - } - - fn get_max_value(&self, f: &str) -> Result { - self.open_path(f, false).and_then(|mut file| { - let mut string = String::new(); - let res = file.read_to_string(&mut string); - match res { - Ok(_) => parse_max_value(&string), - Err(e) => Err(Error::with_cause(ReadFailed(f.to_string()), e)), - } - }) - } - - #[doc(hidden)] - fn path_exists(&self, p: &str) -> bool { - if self.verify_path().is_err() { - return false; - } - - std::path::Path::new(p).exists() - } - } - - pub trait CustomizedAttribute: ControllerInternal { - fn set(&self, key: &str, value: &str) -> Result<()> { - self.open_path(key, true).and_then(|mut file| { - file.write_all(value.as_ref()).map_err(|e| { - Error::with_cause(WriteFailed(key.to_string(), value.to_string()), e) - }) - }) - } - - #[allow(dead_code)] - fn get(&self, key: &str) -> Result { - self.open_path(key, false).and_then(|mut file: File| { - let mut string = String::new(); - match file.read_to_string(&mut string) { - Ok(_) => Ok(string.trim().to_owned()), - Err(e) => Err(Error::with_cause(ReadFailed(key.to_string()), e)), - } - }) - } - } -} - -pub(crate) use crate::sealed::{ControllerInternal, CustomizedAttribute}; - -/// A Controller is a subsystem attached to the control group. -/// -/// Implementors are able to control certain aspects of a control group. -pub trait Controller { - #[doc(hidden)] - fn control_type(&self) -> Controllers; - - /// The file system path to the controller. - fn path(&self) -> &Path; - - /// Root path of the file system to the controller. - fn base(&self) -> &Path; - - /// Apply a set of resources to the Controller, invoking its internal functions to pass the - /// kernel the information. - fn apply(&self, res: &Resources) -> Result<()>; - - /// Create this controller - fn create(&self); - - /// Does this controller already exist? - fn exists(&self) -> bool; - - /// Set notify_on_release - fn set_notify_on_release(&self, enable: bool) -> Result<()>; - - /// Set release_agent - fn set_release_agent(&self, path: &str) -> Result<()>; - - /// Delete the controller. - fn delete(&self) -> Result<()>; - - /// Attach a task to this controller. - fn add_task(&self, pid: &CgroupPid) -> Result<()>; - - /// Attach a task to this controller. - fn add_task_by_tgid(&self, pid: &CgroupPid) -> Result<()>; - - /// set cgroup type. - fn set_cgroup_type(&self, cgroup_type: &str) -> Result<()>; - - /// get cgroup type. - fn get_cgroup_type(&self) -> Result; - - /// Get the list of tasks that this controller has. - fn tasks(&self) -> Vec; - - /// Get the list of procs that this controller has. - fn procs(&self) -> Vec; - - fn v2(&self) -> bool; -} - -impl Controller for T -where - T: ControllerInternal, -{ - fn control_type(&self) -> Controllers { - ControllerInternal::control_type(self) - } - - fn path(&self) -> &Path { - self.get_path() - } - - fn base(&self) -> &Path { - self.get_base() - } - - /// Apply a set of resources to the Controller, invoking its internal functions to pass the - /// kernel the information. - fn apply(&self, res: &Resources) -> Result<()> { - ControllerInternal::apply(self, res) - } - - /// Create this controller - fn create(&self) { - self.verify_path() - .unwrap_or_else(|_| panic!("path should be valid: {:?}", self.path())); - - match ::std::fs::create_dir_all(self.get_path()) { - Ok(_) => self.post_create(), - Err(e) => warn!("error create_dir: {:?} error: {:?}", self.get_path(), e), - } - } - - /// Set notify_on_release - fn set_notify_on_release(&self, enable: bool) -> Result<()> { - if self.is_v2() { - return Err(Error::new(ErrorKind::CgroupVersion)); - } - self.open_path("notify_on_release", true) - .and_then(|mut file| { - write!(file, "{}", enable as i32).map_err(|e| { - Error::with_cause( - ErrorKind::WriteFailed("notify_on_release".to_string(), enable.to_string()), - e, - ) - }) - }) - } - - /// Set release_agent - fn set_release_agent(&self, path: &str) -> Result<()> { - if self.is_v2() { - return Err(Error::new(ErrorKind::CgroupVersion)); - } - self.open_path("release_agent", true).and_then(|mut file| { - file.write_all(path.as_bytes()).map_err(|e| { - Error::with_cause( - ErrorKind::WriteFailed("release_agent".to_string(), path.to_string()), - e, - ) - }) - }) - } - /// Does this controller already exist? - fn exists(&self) -> bool { - self.get_path().exists() - } - - /// Delete the controller. - fn delete(&self) -> Result<()> { - if !self.get_path().exists() { - return Ok(()); - } - - // Compatible with runC for remove dir operation - // https://github.com/opencontainers/runc/blob/main/libcontainer/cgroups/utils.go#L272 - // - // We trying to remove all paths five times with increasing delay between tries. - // If after all there are not removed cgroups - appropriate error will be - // returned. - let mut delay = std::time::Duration::from_millis(10); - let cgroup_path = self.get_path(); - for _i in 0..4 { - if let Ok(()) = remove_dir(cgroup_path) { - return Ok(()); - } - std::thread::sleep(delay); - delay *= 2; - } - - remove_dir(cgroup_path) - } - - /// Attach a task to this controller. - fn add_task(&self, pid: &CgroupPid) -> Result<()> { - let mut file_name = "tasks"; - if self.is_v2() { - file_name = "cgroup.threads"; - } - self.open_path(file_name, true).and_then(|mut file| { - file.write_all(pid.pid.to_string().as_ref()).map_err(|e| { - Error::with_cause( - ErrorKind::WriteFailed(file_name.to_string(), pid.pid.to_string()), - e, - ) - }) - }) - } - - /// Attach a task to this controller by thread group id. - fn add_task_by_tgid(&self, pid: &CgroupPid) -> Result<()> { - let file_name = "cgroup.procs"; - self.open_path(file_name, true).and_then(|mut file| { - file.write_all(pid.pid.to_string().as_ref()).map_err(|e| { - Error::with_cause( - ErrorKind::WriteFailed(file_name.to_string(), pid.pid.to_string()), - e, - ) - }) - }) - } - - /// Get the list of procs that this controller has. - fn procs(&self) -> Vec { - let file_name = "cgroup.procs"; - self.open_path(file_name, false) - .map(|file| { - let bf = BufReader::new(file); - let mut v = Vec::new(); - for line in bf.lines() { - match line { - Ok(line) => { - let n = line.trim().parse().unwrap_or(0u64); - v.push(n); - } - Err(_) => break, - } - } - v.into_iter().map(CgroupPid::from).collect() - }) - .unwrap_or_default() - } - - /// Get the list of tasks that this controller has. - fn tasks(&self) -> Vec { - let mut file_name = "tasks"; - if self.is_v2() { - file_name = "cgroup.threads"; - } - self.open_path(file_name, false) - .map(|file| { - let bf = BufReader::new(file); - let mut v = Vec::new(); - for line in bf.lines() { - match line { - Ok(line) => { - let n = line.trim().parse().unwrap_or(0u64); - v.push(n); - } - Err(_) => break, - } - } - v.into_iter().map(CgroupPid::from).collect() - }) - .unwrap_or_default() - } - - /// set cgroup.type - fn set_cgroup_type(&self, cgroup_type: &str) -> Result<()> { - if !self.is_v2() { - return Err(Error::new(ErrorKind::CgroupVersion)); - } - let file_name = "cgroup.type"; - self.open_path(file_name, true).and_then(|mut file| { - file.write_all(cgroup_type.as_bytes()).map_err(|e| { - Error::with_cause( - ErrorKind::WriteFailed(file_name.to_string(), cgroup_type.to_string()), - e, - ) - }) - }) - } - - /// get cgroup.type - fn get_cgroup_type(&self) -> Result { - if !self.is_v2() { - return Err(Error::new(ErrorKind::CgroupVersion)); - } - let file_name = "cgroup.type"; - self.open_path(file_name, false).and_then(|mut file: File| { - let mut string = String::new(); - match file.read_to_string(&mut string) { - Ok(_) => Ok(string.trim().to_owned()), - Err(e) => Err(Error::with_cause( - ErrorKind::ReadFailed(file_name.to_string()), - e, - )), - } - }) - } - - fn v2(&self) -> bool { - self.is_v2() - } -} - -// remove_dir aims to remove cgroup path. It does so recursively, -// by removing any subdirectories (sub-cgroups) first. -fn remove_dir(dir: &Path) -> Result<()> { - // try the fast path first. - if fs::remove_dir(dir).is_ok() { - return Ok(()); - } - - if dir.exists() && dir.is_dir() { - for entry in fs::read_dir(dir) - .map_err(|e| Error::with_cause(ReadFailed(dir.display().to_string()), e))? - { - let entry = - entry.map_err(|e| Error::with_cause(ReadFailed(dir.display().to_string()), e))?; - let path = entry.path(); - if path.is_dir() { - remove_dir(&path)?; - } - } - fs::remove_dir(dir).map_err(|e| Error::with_cause(RemoveFailed, e))?; - } - - Ok(()) -} - -#[doc(hidden)] -pub trait ControllIdentifier { - fn controller_type() -> Controllers; -} - -/// Control group hierarchy (right now, only V1 is supported, but in the future Unified will be -/// implemented as well). -pub trait Hierarchy: std::fmt::Debug + Send + Sync { - /// Returns what subsystems are supported by the hierarchy. - fn subsystems(&self) -> Vec; - - /// Returns the root directory of the hierarchy. - fn root(&self) -> PathBuf; - - /// Return a handle to the root control group in the hierarchy. - fn root_control_group(&self) -> Cgroup; - - /// Return a handle to the parent control group in the hierarchy. - fn parent_control_group(&self, path: &str) -> Cgroup; - - fn v2(&self) -> bool; -} - -/// Resource limits for the memory subsystem. -#[derive(Debug, Clone, Eq, PartialEq, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct MemoryResources { - /// How much memory (in bytes) can the kernel consume. - pub kernel_memory_limit: Option, - /// Upper limit of memory usage of the control group's tasks. - pub memory_hard_limit: Option, - /// How much memory the tasks in the control group can use when the system is under memory - /// pressure. - pub memory_soft_limit: Option, - /// How much of the kernel's memory (in bytes) can be used for TCP-related buffers. - pub kernel_tcp_memory_limit: Option, - /// How much memory and swap together can the tasks in the control group use. - pub memory_swap_limit: Option, - /// Controls the tendency of the kernel to swap out parts of the address space of the tasks to - /// disk. Lower value implies less likely. - /// - /// Note, however, that a value of zero does not mean the process is never swapped out. Use the - /// traditional `mlock(2)` system call for that purpose. - pub swappiness: Option, - /// Customized key-value attributes - /// - /// # Usage: - /// ``` - /// let resource = &mut cgroups_rs::Resources::default(); - /// resource.memory.attrs.insert("memory.numa_balancing".to_string(), "true".to_string()); - /// // apply here - /// ``` - pub attrs: HashMap, -} - -/// Resources limits on the number of processes. -#[derive(Debug, Clone, Eq, PartialEq, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct PidResources { - /// The maximum number of processes that can exist in the control group. - /// - /// Note that attaching processes to the control group will still succeed _even_ if the limit - /// would be violated, however forks/clones inside the control group will have with `EAGAIN` if - /// they would violate the limit set here. - pub maximum_number_of_processes: Option, -} - -/// Resources limits about how the tasks can use the CPU. -#[derive(Debug, Clone, Eq, PartialEq, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct CpuResources { - // cpuset - /// A comma-separated list of CPU IDs where the task in the control group can run. Dashes - /// between numbers indicate ranges. - pub cpus: Option, - /// Same syntax as the `cpus` field of this structure, but applies to memory nodes instead of - /// processors. - pub mems: Option, - // cpu - /// Weight of how much of the total CPU time should this control group get. Note that this is - /// hierarchical, so this is weighted against the siblings of this control group. - pub shares: Option, - /// In one `period`, how much can the tasks run in microseconds. - pub quota: Option, - /// Period of time in microseconds. - pub period: Option, - /// This is currently a no-operation. - pub realtime_runtime: Option, - /// This is currently a no-operation. - pub realtime_period: Option, - /// Customized key-value attributes - /// # Usage: - /// ``` - /// let resource = &mut cgroups_rs::Resources::default(); - /// resource.cpu.attrs.insert("cpu.cfs_init_buffer_us".to_string(), "10".to_string()); - /// // apply here - /// ``` - pub attrs: HashMap, -} - -/// A device resource that can be allowed or denied access to. -#[derive(Debug, Clone, Eq, PartialEq, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct DeviceResource { - /// If true, access to the device is allowed, otherwise it's denied. - pub allow: bool, - /// `'c'` for character device, `'b'` for block device; or `'a'` for all devices. - pub devtype: crate::devices::DeviceType, - /// The major number of the device. - pub major: i64, - /// The minor number of the device. - pub minor: i64, - /// Sequence of `'r'`, `'w'` or `'m'`, each denoting read, write or mknod permissions. - pub access: Vec, -} - -/// Limit the usage of devices for the control group's tasks. -#[derive(Debug, Clone, Eq, PartialEq, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct DeviceResources { - /// For each device in the list, the limits in the structure are applied. - pub devices: Vec, -} - -/// Assigned priority for a network device. -#[derive(Debug, Clone, Eq, PartialEq, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct NetworkPriority { - /// The name (as visible in `ifconfig`) of the interface. - pub name: String, - /// Assigned priority. - pub priority: u64, -} - -/// Collections of limits and tags that can be imposed on packets emitted by the tasks in the -/// control group. -#[derive(Debug, Clone, Eq, PartialEq, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct NetworkResources { - /// The networking class identifier to attach to the packets. - /// - /// This can then later be used in iptables and such to have special rules. - pub class_id: Option, - /// Priority of the egress traffic for each interface. - pub priorities: Vec, -} - -/// A hugepage type and its consumption limit for the control group. -#[derive(Debug, Clone, Eq, PartialEq, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct HugePageResource { - /// The size of the hugepage, i.e. `2MB`, `1GB`, etc. - pub size: String, - /// The amount of bytes (of memory consumed by the tasks) that are allowed to be backed by - /// hugepages. - pub limit: u64, -} - -/// Provides the ability to set consumption limit on each type of hugepages. -#[derive(Debug, Clone, Eq, PartialEq, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct HugePageResources { - /// Set a limit of consumption for each hugepages type. - pub limits: Vec, -} - -/// Weight for a particular block device. -#[derive(Debug, Clone, Eq, PartialEq, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct BlkIoDeviceResource { - /// The major number of the device. - pub major: u64, - /// The minor number of the device. - pub minor: u64, - /// The weight of the device against the descendant nodes. - pub weight: Option, - /// The weight of the device against the sibling nodes. - pub leaf_weight: Option, -} - -/// Provides the ability to throttle a device (both byte/sec, and IO op/s) -#[derive(Debug, Clone, Eq, PartialEq, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct BlkIoDeviceThrottleResource { - /// The major number of the device. - pub major: u64, - /// The minor number of the device. - pub minor: u64, - /// The rate. - pub rate: u64, -} - -/// General block I/O resource limits. -#[derive(Debug, Clone, Eq, PartialEq, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct BlkIoResources { - /// The weight of the control group against descendant nodes. - pub weight: Option, - /// The weight of the control group against sibling nodes. - pub leaf_weight: Option, - /// For each device, a separate weight (both normal and leaf) can be provided. - pub weight_device: Vec, - /// Throttled read bytes/second can be provided for each device. - pub throttle_read_bps_device: Vec, - /// Throttled read IO operations per second can be provided for each device. - pub throttle_read_iops_device: Vec, - /// Throttled written bytes/second can be provided for each device. - pub throttle_write_bps_device: Vec, - /// Throttled write IO operations per second can be provided for each device. - pub throttle_write_iops_device: Vec, - - /// Customized key-value attributes - /// # Usage: - /// ``` - /// let resource = &mut cgroups_rs::Resources::default(); - /// resource.blkio.attrs.insert("io.cost.weight".to_string(), "10".to_string()); - /// // apply here - /// ``` - pub attrs: HashMap, -} - -/// The resource limits and constraints that will be set on the control group. -#[derive(Debug, Clone, Eq, PartialEq, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Resources { - /// Memory usage related limits. - pub memory: MemoryResources, - /// Process identifier related limits. - pub pid: PidResources, - /// CPU related limits. - pub cpu: CpuResources, - /// Device related limits. - pub devices: DeviceResources, - /// Network related tags and limits. - pub network: NetworkResources, - /// Hugepages consumption related limits. - pub hugepages: HugePageResources, - /// Block device I/O related limits. - pub blkio: BlkIoResources, -} - -/// A structure representing a `pid`. Currently implementations exist for `u64` and -/// `std::process::Child`. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct CgroupPid { - /// The process identifier - pub pid: u64, -} - -impl From for CgroupPid { - fn from(u: u64) -> CgroupPid { - CgroupPid { pid: u } - } -} - -impl From<&std::process::Child> for CgroupPid { - fn from(u: &std::process::Child) -> CgroupPid { - CgroupPid { pid: u.id() as u64 } - } -} - -impl Subsystem { - fn enter(self, path: &Path) -> Self { - match self { - Subsystem::Pid(mut cont) => Subsystem::Pid({ - cont.get_path_mut().push(path); - cont - }), - Subsystem::Mem(mut cont) => Subsystem::Mem({ - cont.get_path_mut().push(path); - cont - }), - Subsystem::CpuSet(mut cont) => Subsystem::CpuSet({ - cont.get_path_mut().push(path); - cont - }), - Subsystem::CpuAcct(mut cont) => Subsystem::CpuAcct({ - cont.get_path_mut().push(path); - cont - }), - Subsystem::Cpu(mut cont) => Subsystem::Cpu({ - cont.get_path_mut().push(path); - cont - }), - Subsystem::Devices(mut cont) => Subsystem::Devices({ - cont.get_path_mut().push(path); - cont - }), - Subsystem::Freezer(mut cont) => Subsystem::Freezer({ - cont.get_path_mut().push(path); - cont - }), - Subsystem::NetCls(mut cont) => Subsystem::NetCls({ - cont.get_path_mut().push(path); - cont - }), - Subsystem::BlkIo(mut cont) => Subsystem::BlkIo({ - cont.get_path_mut().push(path); - cont - }), - Subsystem::PerfEvent(mut cont) => Subsystem::PerfEvent({ - cont.get_path_mut().push(path); - cont - }), - Subsystem::NetPrio(mut cont) => Subsystem::NetPrio({ - cont.get_path_mut().push(path); - cont - }), - Subsystem::HugeTlb(mut cont) => Subsystem::HugeTlb({ - cont.get_path_mut().push(path); - cont - }), - Subsystem::Rdma(mut cont) => Subsystem::Rdma({ - cont.get_path_mut().push(path); - cont - }), - Subsystem::Systemd(mut cont) => Subsystem::Systemd({ - cont.get_path_mut().push(path); - cont - }), - } - } - - pub fn to_controller(&self) -> &dyn Controller { - match self { - Subsystem::Pid(cont) => cont, - Subsystem::Mem(cont) => cont, - Subsystem::CpuSet(cont) => cont, - Subsystem::CpuAcct(cont) => cont, - Subsystem::Cpu(cont) => cont, - Subsystem::Devices(cont) => cont, - Subsystem::Freezer(cont) => cont, - Subsystem::NetCls(cont) => cont, - Subsystem::BlkIo(cont) => cont, - Subsystem::PerfEvent(cont) => cont, - Subsystem::NetPrio(cont) => cont, - Subsystem::HugeTlb(cont) => cont, - Subsystem::Rdma(cont) => cont, - Subsystem::Systemd(cont) => cont, - } - } - - pub fn controller_name(&self) -> String { - self.to_controller().control_type().to_string() - } -} - -/// The values for `memory.hight` or `pids.max` -#[derive(Eq, PartialEq, Copy, Clone, Debug)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum MaxValue { - /// This value is returned when the text is `"max"`. - Max, - /// When the value is a numerical value, they are returned via this enum field. - Value(i64), -} - -#[allow(clippy::derivable_impls)] -impl Default for MaxValue { - fn default() -> Self { - MaxValue::Max - } -} - -impl MaxValue { - #[allow(clippy::should_implement_trait, clippy::wrong_self_convention)] - fn to_i64(&self) -> i64 { - match self { - MaxValue::Max => -1, - MaxValue::Value(num) => *num, - } - } -} - -impl fmt::Display for MaxValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - MaxValue::Max => write!(f, "max"), - MaxValue::Value(num) => write!(f, "{}", num), - } - } -} - -pub fn parse_max_value(s: &str) -> Result { - if s.trim() == "max" { - return Ok(MaxValue::Max); - } - match s.trim().parse() { - Ok(val) => Ok(MaxValue::Value(val)), - Err(e) => Err(Error::with_cause(ParseError, e)), - } -} - -// Flat keyed -// KEY0 VAL0\n -// KEY1 VAL1\n -pub fn flat_keyed_to_vec(mut file: File) -> Result> { - let mut content = String::new(); - file.read_to_string(&mut content) - .map_err(|e| Error::with_cause(ReadFailed("FIXME: read_string_from".to_string()), e))?; - - let mut v = Vec::new(); - for line in content.lines() { - let parts: Vec<&str> = line.split(' ').collect(); - if parts.len() == 2 { - if let Ok(i) = parts[1].parse::() { - v.push((parts[0].to_string(), i)); - } - } - } - Ok(v) -} - -// Flat keyed -// KEY0 VAL0\n -// KEY1 VAL1\n -pub fn flat_keyed_to_hashmap(mut file: File) -> Result> { - let mut content = String::new(); - file.read_to_string(&mut content) - .map_err(|e| Error::with_cause(ReadFailed("FIXME: read_string_from".to_string()), e))?; - - let mut h = HashMap::new(); - for line in content.lines() { - let parts: Vec<&str> = line.split(' ').collect(); - if parts.len() == 2 { - if let Ok(i) = parts[1].parse::() { - h.insert(parts[0].to_string(), i); - } - } - } - Ok(h) -} - -// Nested keyed -// KEY0 SUB_KEY0=VAL00 SUB_KEY1=VAL01... -// KEY1 SUB_KEY0=VAL10 SUB_KEY1=VAL11... -pub fn nested_keyed_to_hashmap(mut file: File) -> Result>> { - let mut content = String::new(); - file.read_to_string(&mut content) - .map_err(|e| Error::with_cause(ReadFailed("FIXME: read_string_from".to_string()), e))?; - - let mut h = HashMap::new(); - for line in content.lines() { - let parts: Vec<&str> = line.split(' ').collect(); - if parts.is_empty() { - continue; - } - let mut th = HashMap::new(); - for item in parts[1..].iter() { - let fields: Vec<&str> = item.split('=').collect(); - if fields.len() == 2 { - if let Ok(i) = fields[1].parse::() { - th.insert(fields[0].to_string(), i); - } - } - } - h.insert(parts[0].to_string(), th); - } - - Ok(h) -} - -fn read_from(mut file: File) -> Result -where - T: FromStr, - ::Err: 'static + Send + Sync + std::error::Error, -{ - let mut string = String::new(); - match file.read_to_string(&mut string) { - Ok(_) => string - .trim() - .parse::() - .map_err(|e| Error::with_cause(ParseError, e)), - Err(e) => Err(Error::with_cause( - ReadFailed("FIXME: can't get path in fn read_from".to_string()), - e, - )), - } -} - -fn read_string_from(mut file: File) -> Result { - let mut string = String::new(); - match file.read_to_string(&mut string) { - Ok(_) => Ok(string.trim().to_string()), - Err(e) => Err(Error::with_cause( - ReadFailed("FIXME: can't get path in fn read_string_from".to_string()), - e, - )), - } -} - -/// read and parse an u64 data -fn read_u64_from(file: File) -> Result { - read_from::(file) -} - -/// read and parse an i64 data -fn read_i64_from(file: File) -> Result { - read_from::(file) -} +pub mod fs; diff --git a/tests/builder.rs b/tests/builder.rs index afce133..622f446 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -5,19 +5,19 @@ // //! Some simple tests covering the builder pattern for control groups. -use cgroups_rs::blkio::*; -use cgroups_rs::cgroup_builder::*; -use cgroups_rs::cpu::*; -use cgroups_rs::devices::*; -use cgroups_rs::hugetlb::*; -use cgroups_rs::memory::*; -use cgroups_rs::net_cls::*; -use cgroups_rs::pid::*; -use cgroups_rs::*; +use cgroups_rs::fs::blkio::*; +use cgroups_rs::fs::cgroup_builder::*; +use cgroups_rs::fs::cpu::*; +use cgroups_rs::fs::devices::*; +use cgroups_rs::fs::hugetlb::*; +use cgroups_rs::fs::memory::*; +use cgroups_rs::fs::net_cls::*; +use cgroups_rs::fs::pid::*; +use cgroups_rs::fs::*; #[test] pub fn test_cpu_res_build() { - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let cg: Cgroup = CgroupBuilder::new("test_cpu_res_build") .cpu() .shares(85) @@ -36,7 +36,7 @@ pub fn test_cpu_res_build() { #[test] pub fn test_memory_res_build() { - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let cg: Cgroup = CgroupBuilder::new("test_memory_res_build") .memory() .kernel_memory_limit(128 * 1024 * 1024) @@ -61,7 +61,7 @@ pub fn test_memory_res_build() { #[test] pub fn test_pid_res_build() { - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let cg: Cgroup = CgroupBuilder::new("test_pid_res_build") .pid() .maximum_number_of_processes(MaxValue::Value(123)) @@ -81,7 +81,7 @@ pub fn test_pid_res_build() { #[test] #[ignore] // ignore this test for now, not sure why my kernel doesn't like it pub fn test_devices_res_build() { - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let cg: Cgroup = CgroupBuilder::new("test_devices_res_build") .devices() .device(1, 6, DeviceType::Char, true, vec![DevicePermissions::Read]) @@ -108,7 +108,7 @@ pub fn test_devices_res_build() { #[test] pub fn test_network_res_build() { - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); if h.v2() { // FIXME add cases for v2 return; @@ -130,7 +130,7 @@ pub fn test_network_res_build() { #[test] pub fn test_hugepages_res_build() { - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); if h.v2() { // FIXME add cases for v2 return; @@ -153,7 +153,7 @@ pub fn test_hugepages_res_build() { #[test] #[ignore] // high version kernel not support `blkio.weight` pub fn test_blkio_res_build() { - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let cg: Cgroup = CgroupBuilder::new("test_blkio_res_build") .blkio() .weight(100) diff --git a/tests/cgroup.rs b/tests/cgroup.rs index c63be25..7c743cb 100644 --- a/tests/cgroup.rs +++ b/tests/cgroup.rs @@ -5,20 +5,22 @@ // //! Simple unit tests about the control groups system. -use cgroups_rs::cgroup::{ - CGROUP_MODE_DOMAIN, CGROUP_MODE_DOMAIN_INVALID, CGROUP_MODE_DOMAIN_THREADED, - CGROUP_MODE_THREADED, -}; -use cgroups_rs::memory::MemController; -use cgroups_rs::Controller; -use cgroups_rs::{Cgroup, CgroupPid, Subsystem}; use std::process::Command; use std::thread::sleep; use std::time::Duration; +use cgroups_rs::fs::cgroup::{ + CGROUP_MODE_DOMAIN, CGROUP_MODE_DOMAIN_INVALID, CGROUP_MODE_DOMAIN_THREADED, + CGROUP_MODE_THREADED, +}; +use cgroups_rs::fs::memory::MemController; +use cgroups_rs::fs::Controller; +use cgroups_rs::fs::{Cgroup, Subsystem}; +use cgroups_rs::CgroupPid; + #[test] fn test_procs_iterator_cgroup() { - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let pid = libc::pid_t::from(nix::unistd::getpid()) as u64; let cg = Cgroup::new(h, String::from("test_procs_iterator_cgroup")).unwrap(); { @@ -42,10 +44,10 @@ fn test_procs_iterator_cgroup() { #[test] fn test_tasks_iterator_cgroup_v1() { - if cgroups_rs::hierarchies::is_cgroup2_unified_mode() { + if cgroups_rs::fs::hierarchies::is_cgroup2_unified_mode() { return; } - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let pid = libc::pid_t::from(nix::unistd::getpid()) as u64; let cg = Cgroup::new(h, String::from("test_tasks_iterator_cgroup_v1")).unwrap(); { @@ -69,23 +71,23 @@ fn test_tasks_iterator_cgroup_v1() { #[test] fn test_tasks_iterator_cgroup_threaded_mode() { - if !cgroups_rs::hierarchies::is_cgroup2_unified_mode() { + if !cgroups_rs::fs::hierarchies::is_cgroup2_unified_mode() { return; } let pid = libc::pid_t::from(nix::unistd::getpid()) as u64; let cg = Cgroup::new( - cgroups_rs::hierarchies::auto(), + cgroups_rs::fs::hierarchies::auto(), String::from("test_tasks_iterator_cgroup_threaded_mode"), ) .unwrap(); let cg_threaded_sub1 = Cgroup::new_with_specified_controllers( - cgroups_rs::hierarchies::auto(), + cgroups_rs::fs::hierarchies::auto(), String::from("test_tasks_iterator_cgroup_threaded_mode/threaded_sub1"), Some(vec![String::from("cpuset"), String::from("cpu")]), ) .unwrap(); let cg_threaded_sub2 = Cgroup::new_with_specified_controllers( - cgroups_rs::hierarchies::auto(), + cgroups_rs::fs::hierarchies::auto(), String::from("test_tasks_iterator_cgroup_threaded_mode/threaded_sub2"), Some(vec![String::from("cpuset"), String::from("cpu")]), ) @@ -163,10 +165,10 @@ fn test_tasks_iterator_cgroup_threaded_mode() { #[test] fn test_kill_cgroup() { - if !cgroups_rs::hierarchies::is_cgroup2_unified_mode() { + if !cgroups_rs::fs::hierarchies::is_cgroup2_unified_mode() { return; } - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let cg = Cgroup::new(h, String::from("test_kill_cgroup")).unwrap(); { // Spawn a proc, don't want to getpid(2) here. @@ -206,10 +208,10 @@ fn test_kill_cgroup() { #[test] fn test_cgroup_with_relative_paths() { - if cgroups_rs::hierarchies::is_cgroup2_unified_mode() { + if cgroups_rs::fs::hierarchies::is_cgroup2_unified_mode() { return; } - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let cgroup_root = h.root(); let cgroup_name = "test_cgroup_with_relative_paths"; @@ -247,10 +249,10 @@ fn test_cgroup_with_relative_paths() { #[test] fn test_cgroup_v2() { - if !cgroups_rs::hierarchies::is_cgroup2_unified_mode() { + if !cgroups_rs::fs::hierarchies::is_cgroup2_unified_mode() { return; } - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let cg = Cgroup::new(h, String::from("test_v2")).unwrap(); let mem_controller: &MemController = cg.controller_of().unwrap(); diff --git a/tests/cpu.rs b/tests/cpu.rs index 2b6afb4..b3db5e1 100644 --- a/tests/cpu.rs +++ b/tests/cpu.rs @@ -4,12 +4,12 @@ // //! Simple unit tests about the CPU control groups system. -use cgroups_rs::cpu::CpuController; -use cgroups_rs::Cgroup; +use cgroups_rs::fs::cpu::CpuController; +use cgroups_rs::fs::Cgroup; #[test] fn test_cfs_quota_and_periods() { - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let cg = Cgroup::new(h, String::from("test_cfs_quota_and_periods")).unwrap(); let cpu_controller: &CpuController = cg.controller_of().unwrap(); diff --git a/tests/cpuset.rs b/tests/cpuset.rs index cecfb63..3045827 100644 --- a/tests/cpuset.rs +++ b/tests/cpuset.rs @@ -3,16 +3,16 @@ // // SPDX-License-Identifier: Apache-2.0 or MIT // - -use cgroups_rs::cpuset::CpuSetController; -use cgroups_rs::error::ErrorKind; -use cgroups_rs::{Cgroup, CgroupPid}; - use std::fs; +use cgroups_rs::fs::cpuset::CpuSetController; +use cgroups_rs::fs::error::ErrorKind; +use cgroups_rs::fs::Cgroup; +use cgroups_rs::CgroupPid; + #[test] fn test_cpuset_memory_pressure_root_cg() { - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let cg = Cgroup::new(h, String::from("test_cpuset_memory_pressure_root_cg")).unwrap(); { let cpuset: &CpuSetController = cg.controller_of().unwrap(); @@ -26,7 +26,7 @@ fn test_cpuset_memory_pressure_root_cg() { #[test] fn test_cpuset_set_cpus() { - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let cg = Cgroup::new(h, String::from("test_cpuset_set_cpus")).unwrap(); { let cpuset: &CpuSetController = cg.controller_of().unwrap(); @@ -63,7 +63,7 @@ fn test_cpuset_set_cpus() { #[test] fn test_cpuset_set_cpus_add_task() { - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let cg = Cgroup::new(h, String::from("test_cpuset_set_cpus_add_task/sub-dir")).unwrap(); let cpuset: &CpuSetController = cg.controller_of().unwrap(); diff --git a/tests/devices.rs b/tests/devices.rs index 83bb309..bfccc20 100644 --- a/tests/devices.rs +++ b/tests/devices.rs @@ -6,17 +6,17 @@ //! Integration tests about the devices subsystem -use cgroups_rs::devices::{DevicePermissions, DeviceType, DevicesController}; -use cgroups_rs::{Cgroup, DeviceResource}; +use cgroups_rs::fs::devices::{DevicePermissions, DeviceType, DevicesController}; +use cgroups_rs::fs::{Cgroup, DeviceResource}; #[test] fn test_devices_parsing() { // now only v2 - if cgroups_rs::hierarchies::is_cgroup2_unified_mode() { + if cgroups_rs::fs::hierarchies::is_cgroup2_unified_mode() { return; } - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let cg = Cgroup::new(h, String::from("test_devices_parsing")).unwrap(); { let devices: &DevicesController = cg.controller_of().unwrap(); diff --git a/tests/hugetlb.rs b/tests/hugetlb.rs index 4319ee5..746237f 100644 --- a/tests/hugetlb.rs +++ b/tests/hugetlb.rs @@ -4,19 +4,19 @@ // //! Integration tests about the hugetlb subsystem -use cgroups_rs::error::*; -use cgroups_rs::hugetlb::{self, HugeTlbController}; -use cgroups_rs::Cgroup; +use cgroups_rs::fs::error::*; +use cgroups_rs::fs::hugetlb::{self, HugeTlbController}; +use cgroups_rs::fs::Cgroup; use std::fs; #[test] fn test_hugetlb_sizes() { // now only v2 - if cgroups_rs::hierarchies::is_cgroup2_unified_mode() { + if cgroups_rs::fs::hierarchies::is_cgroup2_unified_mode() { return; } - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let cg = Cgroup::new(h, String::from("test_hugetlb_sizes")).unwrap(); { let hugetlb_controller: &HugeTlbController = cg.controller_of().unwrap(); diff --git a/tests/memory.rs b/tests/memory.rs index 14374cf..6a158e4 100644 --- a/tests/memory.rs +++ b/tests/memory.rs @@ -4,13 +4,12 @@ // //! Integration tests about the hugetlb subsystem -use cgroups_rs::memory::{MemController, SetMemory}; -use cgroups_rs::Controller; -use cgroups_rs::{Cgroup, MaxValue}; +use cgroups_rs::fs::memory::{MemController, SetMemory}; +use cgroups_rs::fs::{Cgroup, Controller, MaxValue}; #[test] fn test_disable_oom_killer() { - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let cg = Cgroup::new(h, String::from("test_disable_oom_killer")).unwrap(); { let mem_controller: &MemController = cg.controller_of().unwrap(); @@ -35,7 +34,7 @@ fn test_disable_oom_killer() { #[test] fn set_kmem_limit_v1() { - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); if h.v2() { return; } @@ -50,7 +49,7 @@ fn set_kmem_limit_v1() { #[test] fn set_mem_v2() { - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); if !h.v2() { return; } diff --git a/tests/pids.rs b/tests/pids.rs index c555ade..01a64d5 100644 --- a/tests/pids.rs +++ b/tests/pids.rs @@ -5,18 +5,15 @@ // //! Integration tests about the pids subsystem -use cgroups_rs::pid::PidController; -use cgroups_rs::Controller; -use cgroups_rs::{Cgroup, MaxValue}; - +use cgroups_rs::fs::pid::PidController; +use cgroups_rs::fs::{Cgroup, Controller, MaxValue}; +use libc::pid_t; use nix::sys::wait::{waitpid, WaitStatus}; use nix::unistd::{fork, ForkResult}; -use libc::pid_t; - #[test] fn create_and_delete_cgroup() { - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let cg = Cgroup::new(h, String::from("create_and_delete_cgroup")).unwrap(); { let pidcontroller: &PidController = cg.controller_of().unwrap(); @@ -30,7 +27,7 @@ fn create_and_delete_cgroup() { #[test] fn test_pids_current_is_zero() { - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let cg = Cgroup::new(h, String::from("test_pids_current_is_zero")).unwrap(); { let pidcontroller: &PidController = cg.controller_of().unwrap(); @@ -42,7 +39,7 @@ fn test_pids_current_is_zero() { #[test] fn test_pids_events_is_zero() { - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let cg = Cgroup::new(h, String::from("test_pids_events_is_zero")).unwrap(); { let pidcontroller: &PidController = cg.controller_of().unwrap(); @@ -55,7 +52,7 @@ fn test_pids_events_is_zero() { #[test] fn test_pid_events_is_not_zero() { - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let cg = Cgroup::new(h, String::from("test_pid_events_is_not_zero")).unwrap(); { let pids: &PidController = cg.controller_of().unwrap(); diff --git a/tests/resources.rs b/tests/resources.rs index d983cce..76453e6 100644 --- a/tests/resources.rs +++ b/tests/resources.rs @@ -5,12 +5,12 @@ // //! Integration test about setting resources using `apply()` -use cgroups_rs::pid::PidController; -use cgroups_rs::{Cgroup, MaxValue, PidResources, Resources}; +use cgroups_rs::fs::pid::PidController; +use cgroups_rs::fs::{Cgroup, MaxValue, PidResources, Resources}; #[test] fn pid_resources() { - let h = cgroups_rs::hierarchies::auto(); + let h = cgroups_rs::fs::hierarchies::auto(); let cg = Cgroup::new(h, String::from("pid_resources")).unwrap(); { let res = Resources {