cgroups_rs/fs/
hierarchies.rs

1// Copyright (c) 2018 Levente Kurusa
2// Copyright (c) 2020 Ant Group
3//
4// SPDX-License-Identifier: Apache-2.0 or MIT
5//
6
7//! This module represents the various control group hierarchies the Linux kernel supports.
8
9use 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/// Process mounts information.
33///
34/// See `proc(5)` for format details.
35#[derive(Debug, PartialEq, Eq, Hash, Clone)]
36pub struct Mountinfo {
37    /// Mount root directory of the file system.
38    pub mount_root: PathBuf,
39    /// Mount pathname relative to the process's root.
40    pub mount_point: PathBuf,
41    /// Filesystem type (main type with optional sub-type).
42    pub fs_type: (String, Option<String>),
43    /// Superblock options.
44    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
79/// Parses the provided mountinfo file.
80fn 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
97/// Returns mounts information for the current process.
98pub 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/// The standard, original cgroup implementation. Often referred to as "cgroupv1".
106#[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        // The cgroup writeback feature requires cooperation between memcgs and blkcgs
125        // To avoid exceptions, we should add_task for blkcg before memcg(push BlkIo before Mem)
126        // For more Information: https://www.alibabacloud.com/help/doc-detail/155509.htm
127        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        // The freezer functionality is present in V2, but not as a controller,
221        // but apparently as a core functionality. FreezerController supports
222        // that, but we must explicitly fake the controller here.
223        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    /// Finds where control groups are mounted to and returns a hierarchy in which control groups
300    /// can be created.
301    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    /// Finds where control groups are mounted to and returns a hierarchy in which control groups
325    /// can be created.
326    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}