Skip to main content

libcgroups/v2/
manager.rs

1use std::fs::{self};
2use std::os::unix::fs::PermissionsExt;
3use std::path::Component::RootDir;
4use std::path::{Path, PathBuf};
5use std::time::Duration;
6
7use nix::unistd::Pid;
8
9use super::controller::Controller;
10use super::controller_type::{
11    CONTROLLER_TYPES, ControllerType, PSEUDO_CONTROLLER_TYPES, PseudoControllerType,
12};
13use super::cpu::{Cpu, V2CpuControllerError, V2CpuStatsError};
14use super::cpuset::CpuSet;
15#[cfg(feature = "cgroupsv2_devices")]
16use super::devices::Devices;
17use super::freezer::{Freezer, V2FreezerError};
18use super::hugetlb::{HugeTlb, V2HugeTlbControllerError, V2HugeTlbStatsError};
19use super::io::{Io, V2IoControllerError, V2IoStatsError};
20use super::memory::{Memory, V2MemoryControllerError, V2MemoryStatsError};
21use super::pids::Pids;
22use super::unified::{Unified, V2UnifiedError};
23use super::util::{self, CGROUP_SUBTREE_CONTROL, V2UtilError};
24use crate::common::{
25    self, AnyCgroupManager, CGROUP_PROCS, CgroupManager, ControllerOpt, FreezerState,
26    JoinSafelyError, PathBufExt, WrapIoResult, WrappedIoError,
27};
28use crate::stats::{PidStatsError, Stats, StatsProvider};
29
30pub const CGROUP_KILL: &str = "cgroup.kill";
31
32#[derive(thiserror::Error, Debug)]
33pub enum V2ManagerError {
34    #[error("io error: {0}")]
35    WrappedIo(#[from] WrappedIoError),
36    #[error("while joining paths: {0}")]
37    JoinSafely(#[from] JoinSafelyError),
38    #[error(transparent)]
39    Util(#[from] V2UtilError),
40
41    #[error(transparent)]
42    CpuController(#[from] V2CpuControllerError),
43    #[error(transparent)]
44    CpuSetController(WrappedIoError),
45    #[error(transparent)]
46    HugeTlbController(#[from] V2HugeTlbControllerError),
47    #[error(transparent)]
48    IoController(#[from] V2IoControllerError),
49    #[error(transparent)]
50    MemoryController(#[from] V2MemoryControllerError),
51    #[error(transparent)]
52    PidsController(WrappedIoError),
53    #[error(transparent)]
54    UnifiedController(#[from] V2UnifiedError),
55    #[error(transparent)]
56    FreezerController(#[from] V2FreezerError),
57    #[cfg(feature = "cgroupsv2_devices")]
58    #[error(transparent)]
59    DevicesController(#[from] super::devices::controller::DevicesControllerError),
60
61    #[error(transparent)]
62    CpuStats(#[from] V2CpuStatsError),
63    #[error(transparent)]
64    HugeTlbStats(#[from] V2HugeTlbStatsError),
65    #[error(transparent)]
66    PidsStats(PidStatsError),
67    #[error(transparent)]
68    MemoryStats(#[from] V2MemoryStatsError),
69    #[error(transparent)]
70    IoStats(#[from] V2IoStatsError),
71}
72
73/// Represents a management interface for a cgroup located at `{root_path}/{cgroup_path}`
74///
75/// This struct does not have ownership of the cgroup
76pub struct Manager {
77    root_path: PathBuf,
78    cgroup_path: PathBuf,
79    full_path: PathBuf,
80}
81
82impl Manager {
83    /// Constructs a new cgroup manager with root path being the mount point
84    /// of a cgroup v2 fs and cgroup path being a relative path from the root
85    pub fn new(root_path: PathBuf, cgroup_path: PathBuf) -> Result<Self, V2ManagerError> {
86        let full_path = root_path.join_safely(&cgroup_path)?;
87
88        Ok(Self {
89            root_path,
90            cgroup_path,
91            full_path,
92        })
93    }
94
95    /// Creates a unified cgroup at `self.full_path` and attaches a process to it
96    fn create_unified_cgroup(&self, pid: Pid) -> Result<(), V2ManagerError> {
97        let controllers: Vec<String> = util::get_available_controllers(&self.root_path)?
98            .iter()
99            .map(|c| format!("+{c}"))
100            .collect();
101
102        Self::write_controllers(&self.root_path, &controllers)?;
103
104        let mut current_path = self.root_path.clone();
105        let mut components = self
106            .cgroup_path
107            .components()
108            .filter(|c| c.ne(&RootDir))
109            .peekable();
110        while let Some(component) = components.next() {
111            current_path = current_path.join(component);
112            if !current_path.exists() {
113                fs::create_dir(&current_path).wrap_create_dir(&current_path)?;
114                fs::metadata(&current_path)
115                    .wrap_other(&current_path)?
116                    .permissions()
117                    .set_mode(0o755);
118            }
119
120            // last component cannot have subtree_control enabled due to internal process constraint
121            // if this were set, writing to the cgroups.procs file will fail with Erno 16 (device or resource busy)
122            if components.peek().is_some() {
123                Self::write_controllers(&current_path, &controllers)?;
124            }
125        }
126
127        common::write_cgroup_file(self.full_path.join(CGROUP_PROCS), pid)?;
128        Ok(())
129    }
130
131    /// Writes a list of controllers to the `{path}/cgroup.subtree_control` file
132    fn write_controllers(path: &Path, controllers: &[String]) -> Result<(), WrappedIoError> {
133        for controller in controllers {
134            common::write_cgroup_file_str(path.join(CGROUP_SUBTREE_CONTROL), controller)?;
135        }
136
137        Ok(())
138    }
139
140    pub fn any(self) -> AnyCgroupManager {
141        AnyCgroupManager::V2(self)
142    }
143}
144
145impl CgroupManager for Manager {
146    type Error = V2ManagerError;
147
148    fn add_task(&self, pid: Pid) -> Result<(), Self::Error> {
149        if self.full_path.exists() {
150            common::write_cgroup_file(self.full_path.join(CGROUP_PROCS), pid)?;
151            return Ok(());
152        }
153        self.create_unified_cgroup(pid)?;
154        Ok(())
155    }
156
157    fn apply(&self, controller_opt: &ControllerOpt) -> Result<(), Self::Error> {
158        for controller in CONTROLLER_TYPES {
159            match controller {
160                ControllerType::Cpu => Cpu::apply(controller_opt, &self.full_path)?,
161                ControllerType::CpuSet => CpuSet::apply(controller_opt, &self.full_path)?,
162                ControllerType::HugeTlb => HugeTlb::apply(controller_opt, &self.full_path)?,
163                ControllerType::Io => Io::apply(controller_opt, &self.full_path)?,
164                ControllerType::Memory => Memory::apply(controller_opt, &self.full_path)?,
165                ControllerType::Pids => Pids::apply(controller_opt, &self.full_path)?,
166            }
167        }
168
169        #[cfg(feature = "cgroupsv2_devices")]
170        Devices::apply(controller_opt, &self.full_path)?;
171
172        for pseudoctlr in PSEUDO_CONTROLLER_TYPES {
173            if let PseudoControllerType::Unified = pseudoctlr {
174                Unified::apply(
175                    controller_opt,
176                    &self.full_path,
177                    util::get_available_controllers(&self.root_path)?,
178                )?;
179            }
180        }
181
182        Ok(())
183    }
184
185    fn remove(&self) -> Result<(), Self::Error> {
186        if self.full_path.exists() {
187            tracing::debug!("remove cgroup {:?}", self.full_path);
188            let kill_file = self.full_path.join(CGROUP_KILL);
189            if kill_file.exists() {
190                fs::write(&kill_file, "1").wrap_write(&kill_file, "1")?;
191            } else {
192                let procs_path = self.full_path.join(CGROUP_PROCS);
193                let procs = fs::read_to_string(&procs_path).wrap_read(&procs_path)?;
194
195                for line in procs.lines() {
196                    let pid: i32 = line
197                        .parse()
198                        .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))
199                        .wrap_other(&procs_path)?;
200                    let _ = nix::sys::signal::kill(Pid::from_raw(pid), nix::sys::signal::SIGKILL);
201                }
202            }
203
204            common::delete_with_retry(&self.full_path, 4, Duration::from_millis(100))?;
205        }
206
207        Ok(())
208    }
209
210    fn freeze(&self, state: FreezerState) -> Result<(), Self::Error> {
211        let controller_opt = ControllerOpt {
212            resources: &Default::default(),
213            freezer_state: Some(state),
214            oom_score_adj: None,
215            disable_oom_killer: false,
216        };
217        Ok(Freezer::apply(&controller_opt, &self.full_path)?)
218    }
219
220    fn stats(&self) -> Result<Stats, Self::Error> {
221        let mut stats = Stats::default();
222
223        for subsystem in CONTROLLER_TYPES {
224            match subsystem {
225                ControllerType::Cpu => stats.cpu = Cpu::stats(&self.full_path)?,
226                ControllerType::HugeTlb => stats.hugetlb = HugeTlb::stats(&self.full_path)?,
227                ControllerType::Pids => {
228                    stats.pids = Pids::stats(&self.full_path).map_err(V2ManagerError::PidsStats)?
229                }
230                ControllerType::Memory => stats.memory = Memory::stats(&self.full_path)?,
231                ControllerType::Io => stats.blkio = Io::stats(&self.full_path)?,
232                _ => continue,
233            }
234        }
235
236        Ok(stats)
237    }
238
239    fn get_all_pids(&self) -> Result<Vec<Pid>, Self::Error> {
240        Ok(common::get_all_pids(&self.full_path)?)
241    }
242}