1use std::fs;
10use std::fs::File;
11use std::io::{BufRead, BufReader};
12use std::path::{Path, PathBuf};
13
14use crate::fs::blkio::BlkIoController;
15use crate::fs::cpu::CpuController;
16use crate::fs::cpuacct::CpuAcctController;
17use crate::fs::cpuset::CpuSetController;
18use crate::fs::devices::DevicesController;
19use crate::fs::freezer::FreezerController;
20use crate::fs::hugetlb::HugeTlbController;
21use crate::fs::memory::MemController;
22use crate::fs::net_cls::NetClsController;
23use crate::fs::net_prio::NetPrioController;
24use crate::fs::perf_event::PerfEventController;
25use crate::fs::pid::PidController;
26use crate::fs::rdma::RdmaController;
27use crate::fs::systemd::SystemdController;
28use crate::fs::{Controllers, Hierarchy, Subsystem};
29
30use crate::fs::cgroup::Cgroup;
31
32#[derive(Debug, PartialEq, Eq, Hash, Clone)]
36pub struct Mountinfo {
37 pub mount_root: PathBuf,
39 pub mount_point: PathBuf,
41 pub fs_type: (String, Option<String>),
43 pub super_opts: Vec<String>,
45}
46
47pub(crate) fn parse_mountinfo_for_line(line: &str) -> Option<Mountinfo> {
48 let s_values: Vec<_> = line.split(" - ").collect();
49 if s_values.len() != 2 {
50 return None;
51 }
52
53 let s0_values: Vec<_> = s_values[0].trim().split(' ').collect();
54 let s1_values: Vec<_> = s_values[1].trim().split(' ').collect();
55 if s0_values.len() < 6 || s1_values.len() < 3 {
56 return None;
57 }
58 let mount_point = PathBuf::from(s0_values[4]);
59 let mount_root = PathBuf::from(s0_values[3]);
60 let fs_type_values: Vec<_> = s1_values[0].trim().split('.').collect();
61 let fs_type = match fs_type_values.len() {
62 1 => (fs_type_values[0].to_string(), None),
63 2 => (
64 fs_type_values[0].to_string(),
65 Some(fs_type_values[1].to_string()),
66 ),
67 _ => return None,
68 };
69
70 let super_opts: Vec<String> = s1_values[2].trim().split(',').map(String::from).collect();
71 Some(Mountinfo {
72 mount_root,
73 mount_point,
74 fs_type,
75 super_opts,
76 })
77}
78
79fn mountinfo_file(file: &mut File) -> Vec<Mountinfo> {
81 let mut r = Vec::new();
82 for line in BufReader::new(file).lines() {
83 match line {
84 Ok(line) => {
85 if let Some(mi) = parse_mountinfo_for_line(&line) {
86 if mi.fs_type.0 == "cgroup" {
87 r.push(mi);
88 }
89 }
90 }
91 Err(_) => break,
92 }
93 }
94 r
95}
96
97pub fn mountinfo_self() -> Vec<Mountinfo> {
99 match File::open("/proc/self/mountinfo") {
100 Ok(mut file) => mountinfo_file(&mut file),
101 Err(_) => vec![],
102 }
103}
104
105#[derive(Debug, Clone)]
107pub struct V1 {
108 mountinfo: Vec<Mountinfo>,
109}
110
111#[derive(Debug, Clone)]
112pub struct V2 {
113 root: String,
114}
115
116impl Hierarchy for V1 {
117 fn v2(&self) -> bool {
118 false
119 }
120
121 fn subsystems(&self) -> Vec<Subsystem> {
122 let mut subs = vec![];
123
124 if let Some((point, root)) = self.get_mount_point(Controllers::BlkIo) {
128 subs.push(Subsystem::BlkIo(BlkIoController::new(point, root, false)));
129 }
130 if let Some((point, root)) = self.get_mount_point(Controllers::Mem) {
131 subs.push(Subsystem::Mem(MemController::new(point, root, false)));
132 }
133 if let Some((point, root)) = self.get_mount_point(Controllers::Pids) {
134 subs.push(Subsystem::Pid(PidController::new(point, root, false)));
135 }
136 if let Some((point, root)) = self.get_mount_point(Controllers::CpuSet) {
137 subs.push(Subsystem::CpuSet(CpuSetController::new(point, root, false)));
138 }
139 if let Some((point, root)) = self.get_mount_point(Controllers::CpuAcct) {
140 subs.push(Subsystem::CpuAcct(CpuAcctController::new(point, root)));
141 }
142 if let Some((point, root)) = self.get_mount_point(Controllers::Cpu) {
143 subs.push(Subsystem::Cpu(CpuController::new(point, root, false)));
144 }
145 if let Some((point, root)) = self.get_mount_point(Controllers::Devices) {
146 subs.push(Subsystem::Devices(DevicesController::new(point, root)));
147 }
148 if let Some((point, root)) = self.get_mount_point(Controllers::Freezer) {
149 subs.push(Subsystem::Freezer(FreezerController::new(
150 point, root, false,
151 )));
152 }
153 if let Some((point, root)) = self.get_mount_point(Controllers::NetCls) {
154 subs.push(Subsystem::NetCls(NetClsController::new(point, root)));
155 }
156 if let Some((point, root)) = self.get_mount_point(Controllers::PerfEvent) {
157 subs.push(Subsystem::PerfEvent(PerfEventController::new(point, root)));
158 }
159 if let Some((point, root)) = self.get_mount_point(Controllers::NetPrio) {
160 subs.push(Subsystem::NetPrio(NetPrioController::new(point, root)));
161 }
162 if let Some((point, root)) = self.get_mount_point(Controllers::HugeTlb) {
163 subs.push(Subsystem::HugeTlb(HugeTlbController::new(
164 point, root, false,
165 )));
166 }
167 if let Some((point, root)) = self.get_mount_point(Controllers::Rdma) {
168 subs.push(Subsystem::Rdma(RdmaController::new(point, root)));
169 }
170 if let Some((point, root)) = self.get_mount_point(Controllers::Systemd) {
171 subs.push(Subsystem::Systemd(SystemdController::new(
172 point, root, false,
173 )));
174 }
175
176 subs
177 }
178
179 fn root_control_group(&self) -> Cgroup {
180 Cgroup::load(auto(), "")
181 }
182
183 fn parent_control_group(&self, path: &str) -> Cgroup {
184 let path = Path::new(path);
185 let parent_path = path.parent().unwrap().to_string_lossy().to_string();
186 Cgroup::load(auto(), parent_path)
187 }
188
189 fn root(&self) -> PathBuf {
190 self.mountinfo
191 .iter()
192 .find_map(|m| {
193 if m.fs_type.0 == "cgroup" {
194 return Some(m.mount_point.parent().unwrap());
195 }
196 None
197 })
198 .unwrap()
199 .to_path_buf()
200 }
201}
202
203impl Hierarchy for V2 {
204 fn v2(&self) -> bool {
205 true
206 }
207
208 fn subsystems(&self) -> Vec<Subsystem> {
209 let p = format!("{}/{}", UNIFIED_MOUNTPOINT, "cgroup.controllers");
210 let ret = fs::read_to_string(p.as_str());
211 if ret.is_err() {
212 return vec![];
213 }
214
215 let mut subs = vec![];
216
217 let controllers = ret.unwrap().trim().to_string();
218 let mut controller_list: Vec<&str> = controllers.split(' ').collect();
219
220 controller_list.push("freezer");
224
225 for s in controller_list {
226 match s {
227 "cpu" => {
228 subs.push(Subsystem::Cpu(CpuController::new(
229 self.root(),
230 PathBuf::from(""),
231 true,
232 )));
233 }
234 "io" => {
235 subs.push(Subsystem::BlkIo(BlkIoController::new(
236 self.root(),
237 PathBuf::from(""),
238 true,
239 )));
240 }
241 "cpuset" => {
242 subs.push(Subsystem::CpuSet(CpuSetController::new(
243 self.root(),
244 PathBuf::from(""),
245 true,
246 )));
247 }
248 "memory" => {
249 subs.push(Subsystem::Mem(MemController::new(
250 self.root(),
251 PathBuf::from(""),
252 true,
253 )));
254 }
255 "pids" => {
256 subs.push(Subsystem::Pid(PidController::new(
257 self.root(),
258 PathBuf::from(""),
259 true,
260 )));
261 }
262 "freezer" => {
263 subs.push(Subsystem::Freezer(FreezerController::new(
264 self.root(),
265 PathBuf::from(""),
266 true,
267 )));
268 }
269 "hugetlb" => {
270 subs.push(Subsystem::HugeTlb(HugeTlbController::new(
271 self.root(),
272 PathBuf::from(""),
273 true,
274 )));
275 }
276 _ => {}
277 }
278 }
279
280 subs
281 }
282
283 fn root_control_group(&self) -> Cgroup {
284 Cgroup::load(auto(), "")
285 }
286
287 fn parent_control_group(&self, path: &str) -> Cgroup {
288 let path = Path::new(path);
289 let parent_path = path.parent().unwrap().to_string_lossy().to_string();
290 Cgroup::load(auto(), parent_path)
291 }
292
293 fn root(&self) -> PathBuf {
294 PathBuf::from(self.root.clone())
295 }
296}
297
298impl V1 {
299 pub fn new() -> V1 {
302 V1 {
303 mountinfo: mountinfo_self(),
304 }
305 }
306
307 pub fn get_mount_point(&self, controller: Controllers) -> Option<(PathBuf, PathBuf)> {
308 self.mountinfo.iter().find_map(|m| {
309 if m.fs_type.0 == "cgroup" && m.super_opts.contains(&controller.to_string()) {
310 return Some((m.mount_point.to_owned(), m.mount_root.to_owned()));
311 }
312 None
313 })
314 }
315}
316
317impl Default for V1 {
318 fn default() -> Self {
319 Self::new()
320 }
321}
322
323impl V2 {
324 pub fn new() -> V2 {
327 V2 {
328 root: String::from(UNIFIED_MOUNTPOINT),
329 }
330 }
331}
332
333impl Default for V2 {
334 fn default() -> Self {
335 Self::new()
336 }
337}
338
339pub const UNIFIED_MOUNTPOINT: &str = "/sys/fs/cgroup";
340
341pub fn is_cgroup2_unified_mode() -> bool {
342 use nix::sys::statfs;
343
344 let path = std::path::Path::new(UNIFIED_MOUNTPOINT);
345 let fs_stat = match statfs::statfs(path) {
346 Ok(fs_stat) => fs_stat,
347 Err(_) => return false,
348 };
349
350 fs_stat.filesystem_type() == statfs::CGROUP2_SUPER_MAGIC
351}
352
353pub fn auto() -> Box<dyn Hierarchy> {
354 if is_cgroup2_unified_mode() {
355 Box::new(V2::new())
356 } else {
357 Box::new(V1::new())
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use super::*;
364
365 #[test]
366 fn test_parse_mount() {
367 let mountinfo = vec![
368 ("29 26 0:26 / /sys/fs/cgroup/cpuset,cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:10 - cgroup cgroup rw,cpuset,cpu,cpuacct",
369 Mountinfo{mount_root: PathBuf::from("/"), mount_point: PathBuf::from("/sys/fs/cgroup/cpuset,cpu,cpuacct"), fs_type: ("cgroup".to_string(), None), super_opts: vec![
370 "rw".to_string(),
371 "cpuset".to_string(),
372 "cpu".to_string(),
373 "cpuacct".to_string(),
374 ]}),
375 ("121 1731 0:42 / /shm rw,nosuid,nodev,noexec,relatime shared:68 master:66 - tmpfs shm rw,size=65536k",
376 Mountinfo{mount_root: PathBuf::from("/"), mount_point: PathBuf::from("/shm"), fs_type: ("tmpfs".to_string(), None), super_opts: vec![
377 "rw".to_string(),
378 "size=65536k".to_string(),
379 ]}),
380 ("121 1731 0:42 / /shm rw,nosuid,nodev,noexec,relatime shared:68 master:66 - tmpfs.123 shm rw,size=65536k",
381 Mountinfo{mount_root: PathBuf::from("/"), mount_point: PathBuf::from("/shm"), fs_type: ("tmpfs".to_string(), Some("123".to_string())), super_opts: vec![
382 "rw".to_string(),
383 "size=65536k".to_string(),
384 ]}),
385 ];
386
387 for mi in mountinfo {
388 let info = parse_mountinfo_for_line(mi.0).unwrap();
389 assert_eq!(info, mi.1)
390 }
391 }
392}