Skip to content

Commit ece4a9a

Browse files
committed
process: Add methods for getting cgroup membership information
1 parent 6a163ad commit ece4a9a

File tree

1 file changed

+67
-0
lines changed

1 file changed

+67
-0
lines changed

src/uu/pgrep/src/process.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,37 @@ impl TryFrom<&String> for RunState {
187187
}
188188
}
189189

190+
/// Represents an entry in `/proc/<pid>/cgroup`.
191+
#[derive(Debug, Clone, PartialEq, Eq)]
192+
pub struct CgroupMembership {
193+
pub hierarchy_id: u32,
194+
pub controllers: Vec<String>,
195+
pub cgroup_path: String,
196+
}
197+
198+
impl TryFrom<&str> for CgroupMembership {
199+
type Error = io::Error;
200+
201+
fn try_from(value: &str) -> Result<Self, Self::Error> {
202+
let parts: Vec<&str> = value.split(':').collect();
203+
if parts.len() != 3 {
204+
return Err(io::ErrorKind::InvalidData.into());
205+
}
206+
207+
Ok(CgroupMembership {
208+
hierarchy_id: parts[0]
209+
.parse::<u32>()
210+
.map_err(|_| io::ErrorKind::InvalidData)?,
211+
controllers: if parts[1].is_empty() {
212+
vec![]
213+
} else {
214+
parts[1].split(',').map(String::from).collect()
215+
},
216+
cgroup_path: parts[2].to_string(),
217+
})
218+
}
219+
}
220+
190221
/// Process ID and its information
191222
#[derive(Debug, Clone, Default, PartialEq, Eq)]
192223
pub struct ProcessInformation {
@@ -383,6 +414,24 @@ impl ProcessInformation {
383414
read_link(format!("/proc/{}/root", self.pid))
384415
}
385416

417+
/// Returns cgroups (both v1 and v2) that the process belongs to.
418+
pub fn cgroups(&mut self) -> Result<Vec<CgroupMembership>, io::Error> {
419+
fs::read_to_string(format!("/proc/{}/cgroup", self.pid))?
420+
.lines()
421+
.map(CgroupMembership::try_from)
422+
.collect()
423+
}
424+
425+
/// Returns path to the v2 cgroup that the process belongs to.
426+
pub fn cgroup_v2_path(&mut self) -> Result<String, io::Error> {
427+
const V2_HIERARCHY_ID: u32 = 0;
428+
self.cgroups()?
429+
.iter()
430+
.find(|cg| cg.hierarchy_id == V2_HIERARCHY_ID)
431+
.map(|cg| cg.cgroup_path.clone())
432+
.ok_or(io::ErrorKind::NotFound.into())
433+
}
434+
386435
/// Fetch run state from [ProcessInformation::cached_stat]
387436
///
388437
/// - [The /proc Filesystem: Table 1-4](https://docs.kernel.org/filesystems/proc.html#id10)
@@ -625,4 +674,22 @@ mod tests {
625674
let mut pid_entry = ProcessInformation::current_process_info().unwrap();
626675
assert_eq!(pid_entry.root().unwrap(), PathBuf::from("/"));
627676
}
677+
678+
#[test]
679+
#[cfg(target_os = "linux")]
680+
fn test_cgroups() {
681+
let mut pid_entry = ProcessInformation::try_new("/proc/1".into()).unwrap();
682+
if pid_entry.name().unwrap() == "systemd" {
683+
let cgroups = pid_entry.cgroups().unwrap();
684+
if let Some(membership) = cgroups.iter().find(|cg| cg.hierarchy_id == 0) {
685+
let expected = CgroupMembership {
686+
hierarchy_id: 0,
687+
controllers: vec![],
688+
cgroup_path: "/init.scope".to_string(),
689+
};
690+
assert_eq!(membership, &expected);
691+
assert_eq!(pid_entry.cgroup_v2_path().unwrap(), "/init.scope");
692+
}
693+
}
694+
}
628695
}

0 commit comments

Comments
 (0)