cgroups_explorer/
explorer.rs1use std::{
2 collections::{HashSet, hash_set},
3 path::{Path, PathBuf},
4};
5
6use cgroups_rs::{
7 Cgroup, Hierarchy,
8 hierarchies::{V1, V2, is_cgroup2_unified_mode},
9};
10use derive_builder::Builder;
11use walkdir::WalkDir;
12
13#[derive(Builder)]
28#[builder(pattern = "owned")]
29pub struct Explorer {
30 hierarchy: Box<dyn Hierarchy>,
32
33 #[builder(field(ty = "Vec<String>", build = "parse_include(self.include)?"))]
35 include: Vec<glob::Pattern>,
36 #[cfg_attr(
38 feature = "regex",
39 builder(default, try_setter, setter(custom, name = "include_regex_str"))
40 )]
41 #[cfg(feature = "regex")]
42 include_regex: Vec<regex::Regex>,
43}
44
45struct CgroupsV2Iterator {
47 walker: walkdir::IntoIter,
48 include: Vec<glob::Pattern>,
49 #[cfg(feature = "regex")]
50 include_regex: Vec<regex::Regex>,
51 base_path: PathBuf,
52}
53
54struct CgroupsV1Iterator {
55 discovered: hash_set::IntoIter<PathBuf>,
56}
57
58impl Explorer {
59 #[must_use]
61 pub fn v1() -> ExplorerBuilder {
62 ExplorerBuilder::default().hierarchy(Box::new(V1::new()))
63 }
64
65 #[must_use]
67 pub fn v2() -> ExplorerBuilder {
68 ExplorerBuilder::default().hierarchy(Box::new(V2::new()))
69 }
70
71 #[must_use]
73 pub fn detect_version() -> ExplorerBuilder {
74 if is_cgroup2_unified_mode() {
75 ExplorerBuilder::default().hierarchy(Box::new(V2::new()))
76 } else {
77 ExplorerBuilder::default().hierarchy(Box::new(V1::new()))
78 }
79 }
80
81 #[must_use]
83 pub fn iter_cgroups(&self) -> Box<dyn Iterator<Item = Cgroup>> {
84 if self.hierarchy.v2() {
85 Box::new(self.iter_cgroups_v2())
86 } else {
87 Box::new(self.iter_cgroups_v1())
88 }
89 }
90
91 fn iter_cgroups_v2(&self) -> CgroupsV2Iterator {
92 let base_path = self.hierarchy.root();
93 let walker = WalkDir::new(base_path.clone())
94 .min_depth(1)
95 .sort_by_file_name()
96 .into_iter();
97 CgroupsV2Iterator {
98 walker,
99 include: self.include.clone(),
100 #[cfg(feature = "regex")]
101 include_regex: self.include_regex.clone(),
102 base_path,
103 }
104 }
105
106 fn iter_cgroups_v1(&self) -> CgroupsV1Iterator {
107 let hierarchy = V1::new();
108 let subystems = hierarchy.subsystems();
109 let base_path = hierarchy.root();
110
111 let mut matching_rel_paths = HashSet::new();
112 for subsystem in subystems {
113 let name = subsystem.controller_name();
114 let walker = WalkDir::new(base_path.join(&name))
115 .min_depth(1)
116 .sort_by_file_name()
117 .into_iter();
118 let base_controller_path = base_path.join(name);
119 for entry in walker {
120 let Ok(entry) = entry else { continue };
121 let path = entry.path();
122 if !entry.file_type().is_dir() {
123 continue;
124 }
125 let Ok(relative_path) = path.strip_prefix(&base_controller_path) else {
126 continue;
127 };
128 if relative_path.components().count() == 0 {
129 continue;
130 }
131 #[cfg(feature = "regex")]
132 let should_include = path_matches_include(&self.include, relative_path)
133 || path_matches_include_regex(&self.include_regex, relative_path);
134 #[cfg(not(feature = "regex"))]
135 let should_include = path_matches_include(&self.include, relative_path);
136
137 if should_include {
138 matching_rel_paths.insert(relative_path.to_path_buf());
139 }
140 }
141 }
142
143 CgroupsV1Iterator {
144 discovered: matching_rel_paths.into_iter(),
145 }
146 }
147}
148
149impl ExplorerBuilder {
150 #[cfg(feature = "regex")]
155 pub fn include_regex_str<S: AsRef<str>>(
156 self,
157 include: &[S],
158 ) -> Result<Self, ExplorerBuilderError> {
159 let include_regex = parse_include_regex(include)
160 .map_err(|e| ExplorerBuilderError::ValidationError(e.to_string()))?;
161 Ok(self.include_regex(include_regex))
162 }
163
164 #[cfg(feature = "regex")]
166 #[must_use]
167 pub fn include_regex(mut self, include: Vec<regex::Regex>) -> Self {
168 if let Some(include_regex) = &mut self.include_regex {
169 include_regex.extend(include);
170 } else {
171 self.include_regex = Some(include);
172 }
173 self
174 }
175}
176
177impl Iterator for CgroupsV2Iterator {
178 type Item = Cgroup;
179
180 fn next(&mut self) -> Option<Self::Item> {
181 loop {
182 let entry = self.walker.next();
183 match entry {
184 Some(Ok(entry)) => {
185 let path = entry.path();
186 if !entry.file_type().is_dir() {
187 continue;
188 }
189 let Ok(relative_path) = path.strip_prefix(&self.base_path) else {
190 continue;
191 };
192 if relative_path.components().count() == 0 {
193 continue;
194 }
195 if !path_matches_include(&self.include, relative_path) {
196 continue;
197 }
198 #[cfg(feature = "regex")]
199 if !path_matches_include_regex(&self.include_regex, relative_path) {
200 continue;
201 }
202 return Some(Cgroup::load(Box::new(V2::new()), relative_path));
203 }
204 Some(Err(_e)) => return None,
205 None => return None,
206 }
207 }
208 }
209}
210
211impl Iterator for CgroupsV1Iterator {
212 type Item = Cgroup;
213
214 fn next(&mut self) -> Option<Self::Item> {
215 self.discovered
216 .next()
217 .map(|path| Cgroup::load(Box::new(V1::new()), path))
218 }
219}
220
221fn path_matches_include(include: &[glob::Pattern], path: &Path) -> bool {
222 if include.is_empty() {
223 return true;
224 }
225 let path_str = path.to_string_lossy();
226 include.iter().any(|pattern| pattern.matches(&path_str))
227}
228
229#[cfg(feature = "regex")]
230fn path_matches_include_regex(include: &[regex::Regex], path: &Path) -> bool {
231 if include.is_empty() {
232 return true;
233 }
234 let path_str = path.to_string_lossy();
235 include.iter().any(|pattern| pattern.is_match(&path_str))
236}
237
238fn parse_include(include: Vec<String>) -> Result<Vec<glob::Pattern>, ExplorerBuilderError> {
239 if include.is_empty() {
240 Ok(Vec::new())
241 } else {
242 include
243 .into_iter()
244 .map(|include| {
245 glob::Pattern::new(&include)
246 .map_err(|e| ExplorerBuilderError::ValidationError(e.to_string()))
247 })
248 .collect()
249 }
250}
251
252#[cfg(feature = "regex")]
253fn parse_include_regex<S: AsRef<str>>(
254 include: &[S],
255) -> Result<Vec<regex::Regex>, ExplorerBuilderError> {
256 if include.is_empty() {
257 Ok(Vec::new())
258 } else {
259 include
260 .iter()
261 .map(|include| {
262 regex::Regex::new(include.as_ref())
263 .map_err(|e| ExplorerBuilderError::ValidationError(e.to_string()))
264 })
265 .collect()
266 }
267}