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::errno::Errno;
8use nix::unistd::Pid;
9
10use super::controller::Controller;
11use super::controller_type::{
12 CONTROLLER_TYPES, ControllerType, PSEUDO_CONTROLLER_TYPES, PseudoControllerType,
13};
14use super::cpu::{Cpu, V2CpuControllerError, V2CpuStatsError};
15use super::cpuset::CpuSet;
16#[cfg(feature = "cgroupsv2_devices")]
17use super::devices::Devices;
18use super::freezer::{Freezer, V2FreezerError};
19use super::hugetlb::{HugeTlb, V2HugeTlbControllerError, V2HugeTlbStatsError};
20use super::io::{Io, V2IoControllerError, V2IoStatsError};
21use super::memory::{Memory, V2MemoryControllerError, V2MemoryStatsError};
22use super::pids::Pids;
23use super::unified::{Unified, V2UnifiedError};
24use super::util::{self, CGROUP_SUBTREE_CONTROL, V2UtilError};
25use crate::common::{
26 self, AnyCgroupManager, CGROUP_PROCS, CgroupManager, ControllerOpt, FreezerState,
27 JoinSafelyError, PathBufExt, WrapIoResult, WrappedIoError,
28};
29use crate::stats::{PidStatsError, Stats, StatsProvider};
30
31pub const CGROUP_KILL: &str = "cgroup.kill";
32
33#[derive(thiserror::Error, Debug)]
34pub enum V2ManagerError {
35 #[error("io error: {0}")]
36 WrappedIo(#[from] WrappedIoError),
37 #[error("while joining paths: {0}")]
38 JoinSafely(#[from] JoinSafelyError),
39 #[error(transparent)]
40 Util(#[from] V2UtilError),
41
42 #[error(transparent)]
43 CpuController(#[from] V2CpuControllerError),
44 #[error(transparent)]
45 CpuSetController(WrappedIoError),
46 #[error(transparent)]
47 HugeTlbController(#[from] V2HugeTlbControllerError),
48 #[error(transparent)]
49 IoController(#[from] V2IoControllerError),
50 #[error(transparent)]
51 MemoryController(#[from] V2MemoryControllerError),
52 #[error(transparent)]
53 PidsController(WrappedIoError),
54 #[error(transparent)]
55 UnifiedController(#[from] V2UnifiedError),
56 #[error(transparent)]
57 FreezerController(#[from] V2FreezerError),
58 #[cfg(feature = "cgroupsv2_devices")]
59 #[error(transparent)]
60 DevicesController(#[from] super::devices::controller::DevicesControllerError),
61
62 #[error(transparent)]
63 CpuStats(#[from] V2CpuStatsError),
64 #[error(transparent)]
65 HugeTlbStats(#[from] V2HugeTlbStatsError),
66 #[error(transparent)]
67 PidsStats(PidStatsError),
68 #[error(transparent)]
69 MemoryStats(#[from] V2MemoryStatsError),
70 #[error(transparent)]
71 IoStats(#[from] V2IoStatsError),
72}
73
74pub struct Manager {
78 root_path: PathBuf,
79 cgroup_path: PathBuf,
80 full_path: PathBuf,
81}
82
83impl Manager {
84 pub fn new(root_path: PathBuf, cgroup_path: PathBuf) -> Result<Self, V2ManagerError> {
87 let full_path = root_path.join_safely(&cgroup_path)?;
88
89 Ok(Self {
90 root_path,
91 cgroup_path,
92 full_path,
93 })
94 }
95
96 fn create_unified_cgroup(&self, pid: Pid) -> Result<(), V2ManagerError> {
98 let controllers: Vec<String> = util::get_available_controllers(&self.root_path)?
99 .iter()
100 .map(|c| format!("+{c}"))
101 .collect();
102
103 let mut current_path = self.root_path.clone();
116 let mut components = self
117 .cgroup_path
118 .components()
119 .filter(|c| c.ne(&RootDir))
120 .peekable();
121 while let Some(component) = components.next() {
122 current_path = current_path.join(component);
123 let we_created = if !current_path.exists() {
124 fs::create_dir(¤t_path).wrap_create_dir(¤t_path)?;
125 fs::metadata(¤t_path)
126 .wrap_other(¤t_path)?
127 .permissions()
128 .set_mode(0o755);
129 true
130 } else {
131 false
132 };
133
134 if components.peek().is_some() {
137 Self::write_controllers(¤t_path, &controllers, we_created)?;
147 }
148 }
149
150 common::write_cgroup_file(self.full_path.join(CGROUP_PROCS), pid)?;
151 Ok(())
152 }
153
154 fn is_subtree_control_per_controller_failure(err: &WrappedIoError) -> bool {
176 matches!(
177 err.inner().raw_os_error().map(Errno::from_raw),
178 Some(Errno::EROFS)
179 | Some(Errno::EACCES)
180 | Some(Errno::ENOENT)
181 | Some(Errno::EPERM)
182 | Some(Errno::EOPNOTSUPP)
183 | Some(Errno::EBUSY)
184 )
185 }
186
187 fn write_controllers(
203 path: &Path,
204 controllers: &[String],
205 strict: bool,
206 ) -> Result<(), WrappedIoError> {
207 for controller in controllers {
208 match common::write_cgroup_file_str(path.join(CGROUP_SUBTREE_CONTROL), controller) {
209 Ok(()) => {}
210 Err(e) if !strict && Self::is_subtree_control_per_controller_failure(&e) => {
211 tracing::debug!(
212 path = ?path,
213 controller = %controller,
214 errno = ?e.inner().raw_os_error(),
215 "skipping unsupported controller on pre-existing ancestor cgroup",
216 );
217 }
218 Err(e) => return Err(e),
219 }
220 }
221
222 Ok(())
223 }
224
225 pub fn any(self) -> AnyCgroupManager {
226 AnyCgroupManager::V2(self)
227 }
228}
229
230impl CgroupManager for Manager {
231 type Error = V2ManagerError;
232
233 fn add_task(&self, pid: Pid) -> Result<(), Self::Error> {
234 if self.full_path.exists() {
235 common::write_cgroup_file(self.full_path.join(CGROUP_PROCS), pid)?;
236 return Ok(());
237 }
238 self.create_unified_cgroup(pid)?;
239 Ok(())
240 }
241
242 fn apply(&self, controller_opt: &ControllerOpt) -> Result<(), Self::Error> {
243 for controller in CONTROLLER_TYPES {
244 match controller {
245 ControllerType::Cpu => Cpu::apply(controller_opt, &self.full_path)?,
246 ControllerType::CpuSet => CpuSet::apply(controller_opt, &self.full_path)?,
247 ControllerType::HugeTlb => HugeTlb::apply(controller_opt, &self.full_path)?,
248 ControllerType::Io => Io::apply(controller_opt, &self.full_path)?,
249 ControllerType::Memory => Memory::apply(controller_opt, &self.full_path)?,
250 ControllerType::Pids => Pids::apply(controller_opt, &self.full_path)?,
251 }
252 }
253
254 #[cfg(feature = "cgroupsv2_devices")]
255 Devices::apply(controller_opt, &self.full_path)?;
256
257 for pseudoctlr in PSEUDO_CONTROLLER_TYPES {
258 if let PseudoControllerType::Unified = pseudoctlr {
259 Unified::apply(
260 controller_opt,
261 &self.full_path,
262 util::get_available_controllers(&self.root_path)?,
263 )?;
264 }
265 }
266
267 Ok(())
268 }
269
270 fn remove(&self) -> Result<(), Self::Error> {
271 if self.full_path.exists() {
272 tracing::debug!("remove cgroup {:?}", self.full_path);
273 let kill_file = self.full_path.join(CGROUP_KILL);
274 if kill_file.exists() {
275 fs::write(&kill_file, "1").wrap_write(&kill_file, "1")?;
276 } else {
277 let procs_path = self.full_path.join(CGROUP_PROCS);
278 let procs = fs::read_to_string(&procs_path).wrap_read(&procs_path)?;
279
280 for line in procs.lines() {
281 let pid: i32 = line
282 .parse()
283 .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))
284 .wrap_other(&procs_path)?;
285 let _ = nix::sys::signal::kill(Pid::from_raw(pid), nix::sys::signal::SIGKILL);
286 }
287 }
288
289 common::delete_with_retry(&self.full_path, 4, Duration::from_millis(100))?;
290 }
291
292 Ok(())
293 }
294
295 fn freeze(&self, state: FreezerState) -> Result<(), Self::Error> {
296 let controller_opt = ControllerOpt {
297 resources: &Default::default(),
298 freezer_state: Some(state),
299 oom_score_adj: None,
300 disable_oom_killer: false,
301 };
302 Ok(Freezer::apply(&controller_opt, &self.full_path)?)
303 }
304
305 fn stats(&self) -> Result<Stats, Self::Error> {
306 let mut stats = Stats::default();
307
308 for subsystem in CONTROLLER_TYPES {
309 match subsystem {
310 ControllerType::Cpu => stats.cpu = Cpu::stats(&self.full_path)?,
311 ControllerType::HugeTlb => stats.hugetlb = HugeTlb::stats(&self.full_path)?,
312 ControllerType::Pids => {
313 stats.pids = Pids::stats(&self.full_path).map_err(V2ManagerError::PidsStats)?
314 }
315 ControllerType::Memory => stats.memory = Memory::stats(&self.full_path)?,
316 ControllerType::Io => stats.blkio = Io::stats(&self.full_path)?,
317 _ => continue,
318 }
319 }
320
321 Ok(stats)
322 }
323
324 fn get_all_pids(&self) -> Result<Vec<Pid>, Self::Error> {
325 Ok(common::get_all_pids(&self.full_path)?)
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use std::fs;
332
333 use super::*;
334 use crate::test::set_fixture;
335 use crate::v2::util::CGROUP_CONTROLLERS;
336
337 #[test]
342 fn is_subtree_control_per_controller_failure_matches() {
343 fn wrap(errno: Errno) -> WrappedIoError {
344 WrappedIoError::Write {
345 err: std::io::Error::from_raw_os_error(errno as i32),
346 path: PathBuf::from("/some/cgroup/cgroup.subtree_control"),
347 data: "+cpu".into(),
348 }
349 }
350
351 assert!(Manager::is_subtree_control_per_controller_failure(&wrap(
353 Errno::EROFS
354 )));
355 assert!(Manager::is_subtree_control_per_controller_failure(&wrap(
356 Errno::EACCES
357 )));
358 assert!(Manager::is_subtree_control_per_controller_failure(&wrap(
359 Errno::ENOENT
360 )));
361 assert!(Manager::is_subtree_control_per_controller_failure(&wrap(
362 Errno::EPERM
363 )));
364 assert!(Manager::is_subtree_control_per_controller_failure(&wrap(
365 Errno::EOPNOTSUPP
366 )));
367 assert!(Manager::is_subtree_control_per_controller_failure(&wrap(
368 Errno::EBUSY
369 )));
370
371 assert!(!Manager::is_subtree_control_per_controller_failure(&wrap(
373 Errno::ENOSPC
374 )));
375 assert!(!Manager::is_subtree_control_per_controller_failure(&wrap(
376 Errno::EINVAL
377 )));
378 assert!(!Manager::is_subtree_control_per_controller_failure(&wrap(
379 Errno::EIO
380 )));
381 }
382
383 #[test]
399 fn create_unified_cgroup_skips_root_subtree_control_write() {
400 let tmp = tempfile::tempdir().expect("create temp dir");
401 let root = tmp.path();
402
403 set_fixture(root, CGROUP_CONTROLLERS, "cpu memory pids").expect("write cgroup.controllers");
405
406 let parent = root.join("parent");
408 fs::create_dir(&parent).expect("create parent dir");
409 set_fixture(&parent, CGROUP_SUBTREE_CONTROL, "").expect("write parent subtree_control");
410
411 let leaf = parent.join("leaf");
421 fs::create_dir(&leaf).expect("create leaf dir");
422 set_fixture(&leaf, CGROUP_PROCS, "").expect("write leaf cgroup.procs");
423
424 let manager = Manager::new(root.to_path_buf(), PathBuf::from("/parent/leaf"))
425 .expect("construct manager");
426
427 manager
430 .create_unified_cgroup(Pid::from_raw(0))
431 .expect("create_unified_cgroup succeeds when root subtree_control is absent");
432
433 let procs = fs::read_to_string(leaf.join(CGROUP_PROCS)).expect("read cgroup.procs");
435 assert_eq!(procs.trim(), "0");
436 }
437
438 #[test]
445 fn write_controllers_happy_path_both_modes() {
446 let tmp = tempfile::tempdir().expect("create temp dir");
447 let dir = tmp.path();
448 set_fixture(dir, CGROUP_SUBTREE_CONTROL, "").expect("write subtree_control fixture");
449
450 let controllers = vec!["+cpu".to_string(), "+memory".to_string()];
451
452 Manager::write_controllers(dir, &controllers, true)
454 .expect("strict write_controllers succeeds on writable subtree_control");
455
456 Manager::write_controllers(dir, &controllers, false)
459 .expect("non-strict write_controllers succeeds on writable subtree_control");
460 }
461}