Skip to main content

conmonrs/oom_watcher/
cgroup_path.rs

1//! Where the cgroup of a process is.
2
3use super::{CGROUP_ROOT, OOMWatcher};
4use anyhow::{Context, Result, bail};
5use nix::unistd::Pid;
6use std::{
7    fs::File as StdFile,
8    io::{BufRead, BufReader, ErrorKind},
9    path::{Path, PathBuf},
10};
11use tracing::{debug, trace};
12
13impl OOMWatcher {
14    /// The cgroup v2 path of a process, none if the process is gone.
15    pub(super) fn process_cgroup_subsystem_path_cgroup_v2(pid: Pid) -> Result<Option<PathBuf>> {
16        if let Some(fp) = Self::try_open_cgroup_path(pid)? {
17            return Self::unified_cgroup_path(BufReader::new(fp)).map(Some);
18        }
19
20        Ok(None)
21    }
22
23    /// The cgroupfs path of the unified hierarchy from a `/proc/<pid>/cgroup`
24    /// file. Named cgroup v1 hierarchies, like the one of systemd, are
25    /// listed as well and may come first.
26    pub(super) fn unified_cgroup_path(cgroup_file: impl BufRead) -> Result<PathBuf> {
27        for line in cgroup_file.lines() {
28            let line = line.context("read line from cgroup file")?;
29            if line.starts_with("0::") {
30                return Self::cgroup_v2_path(&line);
31            }
32        }
33        bail!("no unified hierarchy found in cgroup file")
34    }
35
36    /// The cgroupfs path of the unified hierarchy line `0::cgroup-path` of
37    /// `/proc/<pid>/cgroup`. The path may contain colons itself.
38    pub(super) fn cgroup_v2_path(line: &str) -> Result<PathBuf> {
39        let path = Self::strip_cgroup_root(line.splitn(3, ':').nth(2))?;
40        Ok(Path::new(CGROUP_ROOT).join(path))
41    }
42
43    /// The cgroup path relative to the root of its hierarchy.
44    fn strip_cgroup_root(path: Option<&str>) -> Result<&str> {
45        Ok(path
46            .context("no path found in cgroup")?
47            .strip_prefix("/")
48            .context("strip root path prefix")?
49            .trim_end())
50    }
51
52    fn try_open_cgroup_path(pid: Pid) -> Result<Option<StdFile>> {
53        let cgroup_path = PathBuf::from("/proc").join(pid.to_string()).join("cgroup");
54        debug!("Using cgroup path: {}", cgroup_path.display());
55
56        match StdFile::open(&cgroup_path) {
57            Ok(file) => Ok(file.into()),
58            // Short lived processes will not be handled as an error
59            Err(error) if error.kind() == ErrorKind::NotFound => {
60                trace!("Cgroup path not found: {}", cgroup_path.display());
61                Ok(None)
62            }
63            Err(error) => bail!("open cgroup path {}: {}", cgroup_path.display(), error),
64        }
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn cgroup_paths_from_proc_lines() -> Result<()> {
74        let root = Path::new(CGROUP_ROOT);
75        assert_eq!(
76            OOMWatcher::cgroup_v2_path("0::/kubepods.slice/crio-1:2.scope\n")?,
77            root.join("kubepods.slice/crio-1:2.scope")
78        );
79        assert_eq!(OOMWatcher::cgroup_v2_path("0::/")?, root.to_path_buf());
80        assert!(OOMWatcher::cgroup_v2_path("0:").is_err());
81        assert!(OOMWatcher::cgroup_v2_path("0::relative").is_err());
82
83        // A named v1 hierarchy may come before the unified one.
84        assert_eq!(
85            OOMWatcher::unified_cgroup_path(
86                &b"1:name=systemd:/user.slice\n0::/kubepods.slice/ctr\n"[..]
87            )?,
88            root.join("kubepods.slice/ctr")
89        );
90        assert_eq!(
91            OOMWatcher::unified_cgroup_path(&b"0::/ctr\n"[..])?,
92            root.join("ctr")
93        );
94        assert!(OOMWatcher::unified_cgroup_path(&b"1:name=systemd:/x\n"[..]).is_err());
95        Ok(())
96    }
97}