use super::CgroupFileParsingError;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
use std::{fs, io};
#[cfg(not(test))]
const HOST_CGROUP_NAMESPACE_INODE: u64 = 0xEFFFFFFB;
#[cfg(not(test))]
const DEFAULT_CGROUP_NS_PATH: &str = "/proc/self/ns/cgroup";
fn get_inode(path: &Path) -> io::Result<u64> {
let meta = fs::metadata(path)?;
Ok(meta.ino())
}
fn get_cgroup_node_path(
cgroup_v1_base_controller: &str,
cgroup_path: &Path,
cgroup_mount_path: &Path,
) -> Result<PathBuf, CgroupFileParsingError> {
let file = File::open(cgroup_path).map_err(|_| CgroupFileParsingError::CannotOpenFile)?;
let reader = BufReader::new(file);
let mut node_path: Option<PathBuf> = None;
for line in reader.lines() {
let line_content = &line.map_err(|_| CgroupFileParsingError::InvalidFormat)?;
let cgroup_entry: Vec<&str> = line_content.split(':').collect();
if cgroup_entry.len() != 3 {
continue;
}
let controllers: Vec<&str> = cgroup_entry[1].split(',').collect();
if controllers.contains(&cgroup_v1_base_controller) || controllers.contains(&"") {
let matched_operator = if controllers.contains(&cgroup_v1_base_controller) {
cgroup_v1_base_controller
} else {
""
};
let mut path = cgroup_mount_path.join(matched_operator);
path.push(cgroup_entry[2].strip_prefix('/').unwrap_or(cgroup_entry[2])); node_path = Some(path);
if matched_operator == cgroup_v1_base_controller {
break;
}
}
}
node_path.ok_or(CgroupFileParsingError::CgroupNotFound)
}
#[cfg(not(test))]
fn is_host_cgroup_namespace() -> Result<(), ()> {
let cgroup_namespace_inode = get_inode(Path::new(DEFAULT_CGROUP_NS_PATH)).map_err(|_| ())?;
if cgroup_namespace_inode == HOST_CGROUP_NAMESPACE_INODE {
return Err(());
}
Ok(())
}
#[cfg(test)]
fn is_host_cgroup_namespace() -> Result<(), ()> {
Ok(())
}
pub fn get_cgroup_inode(
cgroup_v1_base_controller: &str,
cgroup_path: &Path,
cgroup_mount_path: &Path,
) -> Option<String> {
is_host_cgroup_namespace().ok()?;
let cgroup_mount_path =
get_cgroup_node_path(cgroup_v1_base_controller, cgroup_path, cgroup_mount_path).ok()?;
Some(get_inode(&cgroup_mount_path).ok()?.to_string())
}
#[cfg(test)]
mod tests {
use super::super::CGROUP_V1_BASE_CONTROLLER;
use super::*;
use maplit::hashmap;
#[test]
fn test_cgroup_node_path_parsing() {
let test_root_dir: &Path = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/tests"));
let test_files = hashmap! {
"cgroup.v2" => Ok("/sys/fs/cgroup"),
"cgroup.v2_custom_path" => Ok("/sys/fs/cgroup/custom/path"),
"cgroup.docker" => Ok("/sys/fs/cgroup/memory/docker/9d5b23edb1ba181e8910389a99906598d69ac9a0ead109ee55730cc416d95f7f"),
"cgroup.linux" => Ok("/sys/fs/cgroup/memory/user.slice/user-0.slice/session-14.scope"),
"cgroup.v1_with_id_0" => Ok("/sys/fs/cgroup/memory/user.slice/user-0.slice/session-14.scope"),
"cgroup.multiple_controllers" => Ok("/sys/fs/cgroup/memory/user.slice/user-0.slice/session-14.scope"),
"cgroup.no_memory" => Err(CgroupFileParsingError::CgroupNotFound),
"path/to/cgroup.missing" => Err(CgroupFileParsingError::CannotOpenFile),
"cgroup.invalid_line_container_id" => Err(CgroupFileParsingError::CgroupNotFound),
};
for (&filename, expected_result) in test_files.iter() {
assert_eq!(
get_cgroup_node_path(
CGROUP_V1_BASE_CONTROLLER,
&test_root_dir.join(filename),
Path::new("/sys/fs/cgroup")
),
expected_result.clone().map(PathBuf::from),
"testing file parsing for cgroup node path with file: {filename}"
);
}
}
}