a653rs_linux_core/
cgroup.rs1use std::fs::{self};
12use std::io::BufRead;
13use std::path::{Path, PathBuf};
14use std::time::{Duration, Instant};
15
16use anyhow::{anyhow, bail, Ok};
17use itertools::Itertools;
18use nix::sys::statfs;
19use nix::unistd::Pid;
20use walkdir::WalkDir;
21
22const KILLING_TIMEOUT: Duration = Duration::from_secs(1);
23
24#[derive(Debug)]
30pub struct CGroup {
31 path: PathBuf,
32}
33
34impl CGroup {
35 pub fn new_root<P: AsRef<Path>>(path: P, name: &str) -> anyhow::Result<Self> {
39 trace!("Create cgroup \"{name}\"");
40 if !is_cgroup(path.as_ref())? {
43 bail!("{} is not a valid cgroup", path.as_ref().display());
44 }
45
46 let path = PathBuf::from(path.as_ref()).join(name);
47
48 if path.exists() {
49 bail!("CGroup {path:?} already exists");
50 } else {
51 fs::create_dir(&path)?;
53 }
54
55 Self::import_root(&path)
56 }
57
58 pub fn import_root<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
60 trace!("Import cgroup {}", path.as_ref().display());
61 let path = PathBuf::from(path.as_ref());
62
63 if !is_cgroup(&path)? {
64 bail!("{} is not a valid cgroup", path.display());
65 }
66
67 Ok(CGroup { path })
68 }
69
70 pub fn new(&self, name: &str) -> anyhow::Result<Self> {
72 Self::new_root(&self.path, name)
73 }
74
75 pub fn new_threaded(&self, name: &str) -> anyhow::Result<Self> {
77 let cgroup = Self::new_root(&self.path, name)?;
78 cgroup.set_threaded()?;
79 Ok(cgroup)
80 }
81
82 pub fn mv_proc(&self, pid: Pid) -> anyhow::Result<()> {
84 trace!("Move {pid:?} to {}", self.get_path().display());
85 if !is_cgroup(&self.path)? {
86 bail!("{} is not a valid cgroup", self.path.display());
87 }
88
89 fs::write(self.path.join("cgroup.procs"), pid.to_string())?;
90 Ok(())
91 }
92
93 pub fn mv_thread(&self, pid: Pid) -> anyhow::Result<()> {
95 trace!("Move {pid:?} to {}", self.get_path().display());
96 if !is_cgroup(&self.path)? {
97 bail!("{} is not a valid cgroup", self.path.display());
98 }
99
100 fs::write(self.path.join("cgroup.threads"), pid.to_string())?;
101 Ok(())
102 }
103
104 fn set_threaded(&self) -> anyhow::Result<()> {
106 trace!("Change type of {} to threaded", self.get_path().display());
107 if !is_cgroup(&self.path)? {
108 bail!("{} is not a valid cgroup", self.path.display());
109 }
110
111 fs::write(self.path.join("cgroup.type"), "threaded")?;
112 Ok(())
113 }
114
115 pub fn get_pids(&self) -> anyhow::Result<Vec<Pid>> {
117 if !is_cgroup(&self.path)? {
118 bail!("{} is not a valid cgroup", self.path.display());
119 }
120
121 let pids: Vec<Pid> = fs::read(self.path.join("cgroup.procs"))?
122 .lines()
123 .map(|line| Pid::from_raw(line.unwrap().parse().unwrap()))
124 .collect();
125
126 Ok(pids)
127 }
128
129 pub fn get_tids(&self) -> anyhow::Result<Vec<Pid>> {
131 if !is_cgroup(&self.path)? {
132 bail!("{} is not a valid cgroup", self.path.display());
133 }
134
135 let pids: Vec<Pid> = fs::read(self.path.join("cgroup.threads"))?
136 .lines()
137 .map(|line| Pid::from_raw(line.unwrap().parse().unwrap()))
138 .collect();
139
140 Ok(pids)
141 }
142
143 pub fn populated(&self) -> anyhow::Result<bool> {
145 if !is_cgroup(&self.path)? {
146 bail!("{} is not a valid cgroup", self.path.display());
147 }
148
149 Ok(fs::read_to_string(self.get_events_path())?.contains("populated 1\n"))
150 }
151
152 pub fn frozen(&self) -> anyhow::Result<bool> {
154 if !is_cgroup(&self.path)? {
155 bail!("{} is not a valid cgroup", self.path.display());
156 }
157
158 let path = self.path.join("cgroup.freeze");
161 if !path.exists() {
162 return Ok(false);
163 }
164
165 Ok(fs::read(&path)? == b"1\n")
166 }
167
168 pub fn freeze(&self) -> anyhow::Result<()> {
170 trace!("Freeze {}", self.get_path().display());
171 if !is_cgroup(&self.path)? {
172 bail!("{} is not a valid cgroup", self.path.display());
173 }
174
175 let path = self.path.join("cgroup.freeze");
178 if !path.exists() {
179 bail!("cannot freeze the root cgroup");
180 }
181
182 Ok(fs::write(path, "1")?)
183 }
184
185 pub fn unfreeze(&self) -> anyhow::Result<()> {
187 trace!("Unfreeze {}", self.get_path().display());
188 if !is_cgroup(&self.path)? {
189 bail!("{} is not a valid cgroup", self.path.display());
190 }
191
192 let path = self.path.join("cgroup.freeze");
195 if !path.exists() {
196 bail!("cannot unfreeze the root cgroup");
197 }
198
199 Ok(fs::write(path, "0")?)
200 }
201
202 pub fn kill(&self) -> anyhow::Result<()> {
205 trace!("Kill {}", self.get_path().display());
206 if !is_cgroup(&self.path)? {
207 bail!("{} is not a valid cgroup", self.path.display());
208 }
209
210 let killfile = self.path.join("cgroup.kill");
213 if !killfile.exists() {
214 bail!("cannot kill the root cgroup");
215 }
216
217 trace!("writing '1' to {}", killfile.display());
219 fs::write(killfile, "1")?;
220
221 let start = Instant::now();
223 trace!("Killing with a {KILLING_TIMEOUT:?} timeout");
224 while start.elapsed() < KILLING_TIMEOUT {
225 if !self.populated()? {
226 trace!("Killed with a {KILLING_TIMEOUT:?} timeout");
227 return Ok(());
228 }
229 }
230
231 bail!("failed to kill the cgroup")
232 }
233
234 pub fn get_path(&self) -> PathBuf {
236 self.path.clone()
237 }
238
239 pub fn get_events_path(&self) -> PathBuf {
241 self.path.join("cgroup.events")
242 }
243
244 pub fn rm(&self) -> anyhow::Result<()> {
246 trace!("Remove {}", self.get_path().display());
247 if !is_cgroup(&self.path)? {
248 bail!("{} is not a valid cgroup", self.path.display());
249 }
250
251 self.kill()?;
253
254 trace!("Calling remove on {}", &self.path.display());
258 for d in WalkDir::new(&self.path)
259 .into_iter()
260 .flatten()
261 .filter(|e| e.file_type().is_dir())
262 .sorted_by(|a, b| a.depth().cmp(&b.depth()).reverse())
263 {
264 trace!("Removing cgroup {}", &d.path().display());
265 fs::remove_dir(d.path())?;
266 }
267
268 Ok(())
269 }
270
271 }
273
274pub fn mount_point() -> anyhow::Result<PathBuf> {
276 procfs::process::Process::myself()?
278 .mountinfo()?
279 .into_iter()
280 .find(|m| m.fs_type.eq("cgroup2")) .ok_or_else(|| anyhow!("no cgroup2 mount found"))
282 .map(|m| m.mount_point.clone())
283}
284
285pub fn current_cgroup() -> anyhow::Result<PathBuf> {
288 let path = procfs::process::Process::myself()?
289 .cgroups()?
290 .into_iter()
291 .next()
292 .ok_or(anyhow!("cannot obtain cgroup"))?
293 .pathname
294 .clone();
295 let path = &path[1..path.len()]; Ok(PathBuf::from(path))
298}
299
300fn is_cgroup(path: &Path) -> anyhow::Result<bool> {
302 let st = statfs::statfs(path)?;
303 Ok(st.filesystem_type() == statfs::CGROUP2_SUPER_MAGIC)
304}
305
306#[cfg(test)]
307mod tests {
308 use std::{io, process};
311
312 use super::*;
313
314 #[test]
315 fn new_root() {
316 let name = gen_name();
317 let path = get_path().join(&name);
318 assert!(!path.exists()); let cg = CGroup::new_root(get_path(), &name).unwrap();
321 assert!(path.exists() && path.is_dir());
322
323 cg.rm().unwrap();
324 assert!(!path.exists());
325 }
326
327 #[test]
328 fn import_root() {
329 let path = get_path().join(gen_name());
330 assert!(!path.exists()); fs::create_dir(&path).unwrap();
332
333 let cg = CGroup::import_root(&path).unwrap();
334
335 cg.rm().unwrap();
336 assert!(!path.exists());
337 }
338
339 #[test]
340 fn new() {
341 let name1 = gen_name();
342 let name2 = gen_name();
343
344 let path_cg1 = get_path().join(&name1);
345 let path_cg2 = path_cg1.join(&name2);
346 assert!(!path_cg1.exists()); let cg1 = CGroup::new_root(get_path(), &name1).unwrap();
349 assert!(path_cg1.exists() && path_cg1.is_dir());
350 assert!(!path_cg2.exists());
351
352 let _cg2 = cg1.new(&name2).unwrap();
353 assert!(path_cg2.exists() && path_cg2.is_dir());
354
355 cg1.rm().unwrap();
356
357 assert!(!path_cg2.exists());
358 assert!(!path_cg1.exists());
359 }
360
361 #[test]
362 fn mv() {
363 let mut proc = spawn_proc().unwrap();
364 let pid = Pid::from_raw(proc.id() as i32);
365
366 let cg1 = CGroup::new_root(get_path(), &gen_name()).unwrap();
367 let cg2 = cg1.new(&gen_name()).unwrap();
368
369 cg1.mv_proc(pid).unwrap();
370 cg2.mv_proc(pid).unwrap();
371 proc.kill().unwrap();
372
373 cg1.rm().unwrap();
374 }
375
376 #[test]
377 fn get_pids() {
378 let mut proc = spawn_proc().unwrap();
379 let pid = Pid::from_raw(proc.id() as i32);
380
381 let cg1 = CGroup::new_root(get_path(), &gen_name()).unwrap();
382 let cg2 = cg1.new(&gen_name()).unwrap();
383
384 assert!(cg1.get_pids().unwrap().is_empty());
385 assert!(cg2.get_pids().unwrap().is_empty());
386
387 cg1.mv_proc(pid).unwrap();
388 let pids = cg1.get_pids().unwrap();
389 assert!(!pids.is_empty());
390 assert!(cg2.get_pids().unwrap().is_empty());
391 assert_eq!(pids.len(), 1);
392 assert_eq!(pids[0], pid);
393
394 cg2.mv_proc(pid).unwrap();
395 let pids = cg2.get_pids().unwrap();
396 assert!(!pids.is_empty());
397 assert!(cg1.get_pids().unwrap().is_empty());
398 assert_eq!(pids.len(), 1);
399 assert_eq!(pids[0], pid);
400
401 proc.kill().unwrap();
402
403 cg1.rm().unwrap();
404 }
405
406 #[test]
407 fn populated() {
408 let mut proc = spawn_proc().unwrap();
409 let pid = Pid::from_raw(proc.id() as i32);
410 let cg = CGroup::new_root(get_path(), &gen_name()).unwrap();
411
412 assert!(!cg.populated().unwrap());
413 assert_eq!(cg.populated().unwrap(), !cg.get_pids().unwrap().is_empty());
414
415 cg.mv_proc(pid).unwrap();
416 assert!(cg.populated().unwrap());
417 assert_eq!(cg.populated().unwrap(), !cg.get_pids().unwrap().is_empty());
418
419 proc.kill().unwrap();
420
421 cg.rm().unwrap();
422 }
423
424 #[test]
425 fn frozen() {
426 let mut proc = spawn_proc().unwrap();
427 let pid = Pid::from_raw(proc.id() as i32);
428 let cg = CGroup::new_root(get_path(), &gen_name()).unwrap();
429
430 assert!(!cg.frozen().unwrap());
432 cg.freeze().unwrap();
433 assert!(cg.frozen().unwrap());
434
435 cg.unfreeze().unwrap();
437 assert!(!cg.frozen().unwrap());
438
439 cg.mv_proc(pid).unwrap();
441 cg.freeze().unwrap();
442 assert!(cg.frozen().unwrap());
443 cg.unfreeze().unwrap();
444 assert!(!cg.frozen().unwrap());
445
446 proc.kill().unwrap();
447
448 cg.rm().unwrap();
449 }
450
451 #[test]
452 fn kill() {
453 let proc = spawn_proc().unwrap();
454 let pid = Pid::from_raw(proc.id() as i32);
455 let cg = CGroup::new_root(get_path(), &gen_name()).unwrap();
456
457 cg.kill().unwrap();
459
460 cg.mv_proc(pid).unwrap();
462 assert!(cg.populated().unwrap());
463 cg.kill().unwrap();
464
465 cg.rm().unwrap();
466
467 }
470
471 #[test]
472 fn is_cgroup() {
473 assert!(super::is_cgroup(&get_path()).unwrap());
474 assert!(!super::is_cgroup(Path::new("/tmp")).unwrap());
475 }
476
477 fn spawn_proc() -> io::Result<process::Child> {
479 process::Command::new("sleep")
480 .arg("120")
481 .stdout(process::Stdio::null())
482 .spawn()
483 }
484
485 fn get_path() -> PathBuf {
487 super::mount_point()
488 .unwrap()
489 .join(super::current_cgroup().unwrap())
490 }
491
492 fn gen_name() -> String {
494 loop {
495 let val: u64 = rand::random();
496 let str = format!("apex-test-{val}");
497 if !Path::new(&str).exists() {
498 return str;
499 }
500 }
501 }
502}