cgroups_rs/fs/
mod.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#![allow(clippy::unnecessary_unwrap)]
8
9use std::collections::HashMap;
10use std::fmt;
11use std::fs::{self, File};
12use std::io::{BufRead, BufReader, Read, Write};
13use std::path::{Path, PathBuf};
14use std::str::FromStr;
15
16macro_rules! update_and_test {
17    ($self: ident, $set_func:ident, $value:expr, $get_func:ident) => {
18        if let Some(v) = $value {
19            $self.$set_func(v)?;
20            if $self.$get_func()? != v {
21                return Err(Error::new(Other));
22            }
23        }
24    };
25}
26
27macro_rules! update {
28    ($self: ident, $set_func:ident, $value:expr) => {
29        if let Some(v) = $value {
30            let _ = $self.$set_func(v);
31        }
32    };
33}
34
35pub mod blkio;
36pub mod cgroup;
37pub mod cgroup_builder;
38pub mod cpu;
39pub mod cpuacct;
40pub mod cpuset;
41pub mod devices;
42pub mod error;
43pub mod events;
44pub mod freezer;
45pub mod hierarchies;
46pub mod hugetlb;
47pub mod memory;
48pub mod net_cls;
49pub mod net_prio;
50pub mod perf_event;
51pub mod pid;
52pub mod rdma;
53pub mod systemd;
54
55use crate::fs::blkio::BlkIoController;
56use crate::fs::cpu::CpuController;
57use crate::fs::cpuacct::CpuAcctController;
58use crate::fs::cpuset::CpuSetController;
59use crate::fs::devices::DevicesController;
60use crate::fs::error::ErrorKind::*;
61use crate::fs::error::*;
62use crate::fs::freezer::FreezerController;
63use crate::fs::hugetlb::HugeTlbController;
64use crate::fs::memory::MemController;
65use crate::fs::net_cls::NetClsController;
66use crate::fs::net_prio::NetPrioController;
67use crate::fs::perf_event::PerfEventController;
68use crate::fs::pid::PidController;
69use crate::fs::rdma::RdmaController;
70use crate::fs::systemd::SystemdController;
71
72#[doc(inline)]
73pub use crate::fs::cgroup::Cgroup;
74
75/// Contains all the subsystems that are available in this crate.
76#[derive(Debug, Clone)]
77pub enum Subsystem {
78    /// Controller for the `Pid` subsystem, see `PidController` for more information.
79    Pid(PidController),
80    /// Controller for the `Mem` subsystem, see `MemController` for more information.
81    Mem(MemController),
82    /// Controller for the `CpuSet subsystem, see `CpuSetController` for more information.
83    CpuSet(CpuSetController),
84    /// Controller for the `CpuAcct` subsystem, see `CpuAcctController` for more information.
85    CpuAcct(CpuAcctController),
86    /// Controller for the `Cpu` subsystem, see `CpuController` for more information.
87    Cpu(CpuController),
88    /// Controller for the `Devices` subsystem, see `DevicesController` for more information.
89    Devices(DevicesController),
90    /// Controller for the `Freezer` subsystem, see `FreezerController` for more information.
91    Freezer(FreezerController),
92    /// Controller for the `NetCls` subsystem, see `NetClsController` for more information.
93    NetCls(NetClsController),
94    /// Controller for the `BlkIo` subsystem, see `BlkIoController` for more information.
95    BlkIo(BlkIoController),
96    /// Controller for the `PerfEvent` subsystem, see `PerfEventController` for more information.
97    PerfEvent(PerfEventController),
98    /// Controller for the `NetPrio` subsystem, see `NetPrioController` for more information.
99    NetPrio(NetPrioController),
100    /// Controller for the `HugeTlb` subsystem, see `HugeTlbController` for more information.
101    HugeTlb(HugeTlbController),
102    /// Controller for the `Rdma` subsystem, see `RdmaController` for more information.
103    Rdma(RdmaController),
104    /// Controller for the `Systemd` subsystem, see `SystemdController` for more information.
105    Systemd(SystemdController),
106}
107
108#[doc(hidden)]
109#[derive(Eq, PartialEq, Debug, Clone)]
110pub enum Controllers {
111    Pids,
112    Mem,
113    CpuSet,
114    CpuAcct,
115    Cpu,
116    Devices,
117    Freezer,
118    NetCls,
119    BlkIo,
120    PerfEvent,
121    NetPrio,
122    HugeTlb,
123    Rdma,
124    Systemd,
125}
126
127impl fmt::Display for Controllers {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        match self {
130            Controllers::Pids => write!(f, "pids"),
131            Controllers::Mem => write!(f, "memory"),
132            Controllers::CpuSet => write!(f, "cpuset"),
133            Controllers::CpuAcct => write!(f, "cpuacct"),
134            Controllers::Cpu => write!(f, "cpu"),
135            Controllers::Devices => write!(f, "devices"),
136            Controllers::Freezer => write!(f, "freezer"),
137            Controllers::NetCls => write!(f, "net_cls"),
138            Controllers::BlkIo => write!(f, "blkio"),
139            Controllers::PerfEvent => write!(f, "perf_event"),
140            Controllers::NetPrio => write!(f, "net_prio"),
141            Controllers::HugeTlb => write!(f, "hugetlb"),
142            Controllers::Rdma => write!(f, "rdma"),
143            Controllers::Systemd => write!(f, "name=systemd"),
144        }
145    }
146}
147
148mod sealed {
149    use super::*;
150
151    pub trait ControllerInternal {
152        fn apply(&self, res: &Resources) -> Result<()>;
153
154        // meta stuff
155        fn control_type(&self) -> Controllers;
156        fn get_path(&self) -> &PathBuf;
157        fn get_path_mut(&mut self) -> &mut PathBuf;
158        fn get_base(&self) -> &PathBuf;
159
160        /// Hooks running after controller crated, if have
161        fn post_create(&self) {}
162
163        fn is_v2(&self) -> bool {
164            false
165        }
166
167        fn open_path(&self, p: &str, w: bool) -> Result<File> {
168            let mut path = self.get_path().clone();
169            path.push(p);
170
171            if w {
172                match File::create(&path) {
173                    Err(e) => Err(Error::with_cause(
174                        ErrorKind::WriteFailed(
175                            path.display().to_string(),
176                            "[CREATE FILE]".to_string(),
177                        ),
178                        e,
179                    )),
180                    Ok(file) => Ok(file),
181                }
182            } else {
183                match File::open(&path) {
184                    Err(e) => Err(Error::with_cause(
185                        ErrorKind::ReadFailed(path.display().to_string()),
186                        e,
187                    )),
188                    Ok(file) => Ok(file),
189                }
190            }
191        }
192
193        fn get_max_value(&self, f: &str) -> Result<MaxValue> {
194            self.open_path(f, false).and_then(|mut file| {
195                let mut string = String::new();
196                let res = file.read_to_string(&mut string);
197                match res {
198                    Ok(_) => parse_max_value(&string),
199                    Err(e) => Err(Error::with_cause(ReadFailed(f.to_string()), e)),
200                }
201            })
202        }
203
204        #[doc(hidden)]
205        fn path_exists(&self, p: &str) -> bool {
206            std::path::Path::new(p).exists()
207        }
208    }
209
210    pub trait CustomizedAttribute: ControllerInternal {
211        fn set(&self, key: &str, value: &str) -> Result<()> {
212            self.open_path(key, true).and_then(|mut file| {
213                file.write_all(value.as_ref()).map_err(|e| {
214                    Error::with_cause(WriteFailed(key.to_string(), value.to_string()), e)
215                })
216            })
217        }
218
219        #[allow(dead_code)]
220        fn get(&self, key: &str) -> Result<String> {
221            self.open_path(key, false).and_then(|mut file: File| {
222                let mut string = String::new();
223                match file.read_to_string(&mut string) {
224                    Ok(_) => Ok(string.trim().to_owned()),
225                    Err(e) => Err(Error::with_cause(ReadFailed(key.to_string()), e)),
226                }
227            })
228        }
229    }
230}
231
232pub(crate) use crate::fs::sealed::{ControllerInternal, CustomizedAttribute};
233use crate::CgroupPid;
234
235/// A Controller is a subsystem attached to the control group.
236///
237/// Implementors are able to control certain aspects of a control group.
238pub trait Controller {
239    #[doc(hidden)]
240    fn control_type(&self) -> Controllers;
241
242    /// The file system path to the controller.
243    fn path(&self) -> &Path;
244
245    /// Root path of the file system to the controller.
246    fn base(&self) -> &Path;
247
248    /// Apply a set of resources to the Controller, invoking its internal functions to pass the
249    /// kernel the information.
250    fn apply(&self, res: &Resources) -> Result<()>;
251
252    /// Create this controller
253    fn create(&self) -> Result<()>;
254
255    /// Does this controller already exist?
256    fn exists(&self) -> bool;
257
258    /// Set notify_on_release
259    fn set_notify_on_release(&self, enable: bool) -> Result<()>;
260
261    /// Set release_agent
262    fn set_release_agent(&self, path: &str) -> Result<()>;
263
264    /// Delete the controller.
265    fn delete(&self) -> Result<()>;
266
267    /// Attach a task to this controller.
268    fn add_task(&self, pid: &CgroupPid) -> Result<()>;
269
270    /// Attach a task to this controller.
271    fn add_task_by_tgid(&self, pid: &CgroupPid) -> Result<()>;
272
273    /// set cgroup type.
274    fn set_cgroup_type(&self, cgroup_type: &str) -> Result<()>;
275
276    /// get cgroup type.
277    fn get_cgroup_type(&self) -> Result<String>;
278
279    /// Get the list of tasks that this controller has.
280    fn tasks(&self) -> Vec<CgroupPid>;
281
282    /// Get the list of procs that this controller has.
283    fn procs(&self) -> Vec<CgroupPid>;
284
285    fn v2(&self) -> bool;
286}
287
288impl<T> Controller for T
289where
290    T: ControllerInternal,
291{
292    fn control_type(&self) -> Controllers {
293        ControllerInternal::control_type(self)
294    }
295
296    fn path(&self) -> &Path {
297        self.get_path()
298    }
299
300    fn base(&self) -> &Path {
301        self.get_base()
302    }
303
304    /// Apply a set of resources to the Controller, invoking its internal functions to pass the
305    /// kernel the information.
306    fn apply(&self, res: &Resources) -> Result<()> {
307        ControllerInternal::apply(self, res)
308    }
309
310    /// Create this controller
311    fn create(&self) -> Result<()> {
312        std::fs::create_dir_all(self.get_path())
313            .map_err(|err| Error::with_cause(ErrorKind::FsError, err))?;
314        self.post_create();
315        Ok(())
316    }
317
318    /// Set notify_on_release
319    fn set_notify_on_release(&self, enable: bool) -> Result<()> {
320        if self.is_v2() {
321            return Err(Error::new(ErrorKind::CgroupVersion));
322        }
323        self.open_path("notify_on_release", true)
324            .and_then(|mut file| {
325                write!(file, "{}", enable as i32).map_err(|e| {
326                    Error::with_cause(
327                        ErrorKind::WriteFailed("notify_on_release".to_string(), enable.to_string()),
328                        e,
329                    )
330                })
331            })
332    }
333
334    /// Set release_agent
335    fn set_release_agent(&self, path: &str) -> Result<()> {
336        if self.is_v2() {
337            return Err(Error::new(ErrorKind::CgroupVersion));
338        }
339        self.open_path("release_agent", true).and_then(|mut file| {
340            file.write_all(path.as_bytes()).map_err(|e| {
341                Error::with_cause(
342                    ErrorKind::WriteFailed("release_agent".to_string(), path.to_string()),
343                    e,
344                )
345            })
346        })
347    }
348    /// Does this controller already exist?
349    fn exists(&self) -> bool {
350        self.get_path().exists()
351    }
352
353    /// Delete the controller.
354    fn delete(&self) -> Result<()> {
355        if !self.get_path().exists() {
356            return Ok(());
357        }
358
359        // Compatible with runC for remove dir operation
360        // https://github.com/opencontainers/runc/blob/main/libcontainer/cgroups/utils.go#L272
361        //
362        // We trying to remove all paths five times with increasing delay between tries.
363        // If after all there are not removed cgroups - appropriate error will be
364        // returned.
365        let mut delay = std::time::Duration::from_millis(10);
366        let cgroup_path = self.get_path();
367        for _i in 0..4 {
368            if let Ok(()) = remove_dir(cgroup_path) {
369                return Ok(());
370            }
371            std::thread::sleep(delay);
372            delay *= 2;
373        }
374
375        remove_dir(cgroup_path)
376    }
377
378    /// Attach a task to this controller.
379    fn add_task(&self, pid: &CgroupPid) -> Result<()> {
380        let mut file_name = "tasks";
381        if self.is_v2() {
382            file_name = "cgroup.threads";
383        }
384        self.open_path(file_name, true).and_then(|mut file| {
385            file.write_all(pid.pid.to_string().as_ref()).map_err(|e| {
386                Error::with_cause(
387                    ErrorKind::WriteFailed(file_name.to_string(), pid.pid.to_string()),
388                    e,
389                )
390            })
391        })
392    }
393
394    /// Attach a task to this controller by thread group id.
395    fn add_task_by_tgid(&self, pid: &CgroupPid) -> Result<()> {
396        let file_name = "cgroup.procs";
397        self.open_path(file_name, true).and_then(|mut file| {
398            file.write_all(pid.pid.to_string().as_ref()).map_err(|e| {
399                Error::with_cause(
400                    ErrorKind::WriteFailed(file_name.to_string(), pid.pid.to_string()),
401                    e,
402                )
403            })
404        })
405    }
406
407    /// Get the list of procs that this controller has.
408    fn procs(&self) -> Vec<CgroupPid> {
409        let file_name = "cgroup.procs";
410        self.open_path(file_name, false)
411            .map(|file| {
412                let bf = BufReader::new(file);
413                let mut v = Vec::new();
414                for line in bf.lines() {
415                    match line {
416                        Ok(line) => {
417                            let n = line.trim().parse().unwrap_or(0u64);
418                            v.push(n);
419                        }
420                        Err(_) => break,
421                    }
422                }
423                v.into_iter().map(CgroupPid::from).collect()
424            })
425            .unwrap_or_default()
426    }
427
428    /// Get the list of tasks that this controller has.
429    fn tasks(&self) -> Vec<CgroupPid> {
430        let mut file_name = "tasks";
431        if self.is_v2() {
432            file_name = "cgroup.threads";
433        }
434        self.open_path(file_name, false)
435            .map(|file| {
436                let bf = BufReader::new(file);
437                let mut v = Vec::new();
438                for line in bf.lines() {
439                    match line {
440                        Ok(line) => {
441                            let n = line.trim().parse().unwrap_or(0u64);
442                            v.push(n);
443                        }
444                        Err(_) => break,
445                    }
446                }
447                v.into_iter().map(CgroupPid::from).collect()
448            })
449            .unwrap_or_default()
450    }
451
452    /// set cgroup.type
453    fn set_cgroup_type(&self, cgroup_type: &str) -> Result<()> {
454        if !self.is_v2() {
455            return Err(Error::new(ErrorKind::CgroupVersion));
456        }
457        let file_name = "cgroup.type";
458        self.open_path(file_name, true).and_then(|mut file| {
459            file.write_all(cgroup_type.as_bytes()).map_err(|e| {
460                Error::with_cause(
461                    ErrorKind::WriteFailed(file_name.to_string(), cgroup_type.to_string()),
462                    e,
463                )
464            })
465        })
466    }
467
468    /// get cgroup.type
469    fn get_cgroup_type(&self) -> Result<String> {
470        if !self.is_v2() {
471            return Err(Error::new(ErrorKind::CgroupVersion));
472        }
473        let file_name = "cgroup.type";
474        self.open_path(file_name, false).and_then(|mut file: File| {
475            let mut string = String::new();
476            match file.read_to_string(&mut string) {
477                Ok(_) => Ok(string.trim().to_owned()),
478                Err(e) => Err(Error::with_cause(
479                    ErrorKind::ReadFailed(file_name.to_string()),
480                    e,
481                )),
482            }
483        })
484    }
485
486    fn v2(&self) -> bool {
487        self.is_v2()
488    }
489}
490
491// remove_dir aims to remove cgroup path. It does so recursively,
492// by removing any subdirectories (sub-cgroups) first.
493fn remove_dir(dir: &Path) -> Result<()> {
494    // try the fast path first.
495    if fs::remove_dir(dir).is_ok() {
496        return Ok(());
497    }
498
499    if dir.exists() && dir.is_dir() {
500        for entry in fs::read_dir(dir)
501            .map_err(|e| Error::with_cause(ReadFailed(dir.display().to_string()), e))?
502        {
503            let entry =
504                entry.map_err(|e| Error::with_cause(ReadFailed(dir.display().to_string()), e))?;
505            let path = entry.path();
506            if path.is_dir() {
507                remove_dir(&path)?;
508            }
509        }
510        fs::remove_dir(dir).map_err(|e| Error::with_cause(RemoveFailed, e))?;
511    }
512
513    Ok(())
514}
515
516#[doc(hidden)]
517pub trait ControllIdentifier {
518    fn controller_type() -> Controllers;
519}
520
521/// Control group hierarchy (right now, only V1 is supported, but in the future Unified will be
522/// implemented as well).
523pub trait Hierarchy: std::fmt::Debug + Send + Sync {
524    /// Returns what subsystems are supported by the hierarchy.
525    fn subsystems(&self) -> Vec<Subsystem>;
526
527    /// Returns the root directory of the hierarchy.
528    fn root(&self) -> PathBuf;
529
530    /// Return a handle to the root control group in the hierarchy.
531    fn root_control_group(&self) -> Cgroup;
532
533    /// Return a handle to the parent control group in the hierarchy.
534    fn parent_control_group(&self, path: &str) -> Cgroup;
535
536    fn v2(&self) -> bool;
537}
538
539/// Resource limits for the memory subsystem.
540#[derive(Debug, Clone, Eq, PartialEq, Default)]
541#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
542pub struct MemoryResources {
543    /// How much memory (in bytes) can the kernel consume.
544    pub kernel_memory_limit: Option<i64>,
545    /// Upper limit of memory usage of the control group's tasks.
546    pub memory_hard_limit: Option<i64>,
547    /// How much memory the tasks in the control group can use when the system is under memory
548    /// pressure.
549    pub memory_soft_limit: Option<i64>,
550    /// How much of the kernel's memory (in bytes) can be used for TCP-related buffers.
551    pub kernel_tcp_memory_limit: Option<i64>,
552    /// How much memory and swap together can the tasks in the control group use.
553    pub memory_swap_limit: Option<i64>,
554    /// Controls the tendency of the kernel to swap out parts of the address space of the tasks to
555    /// disk. Lower value implies less likely.
556    ///
557    /// Note, however, that a value of zero does not mean the process is never swapped out. Use the
558    /// traditional `mlock(2)` system call for that purpose.
559    pub swappiness: Option<u64>,
560    /// Customized key-value attributes
561    ///
562    /// # Usage:
563    /// ```
564    /// let resource = &mut cgroups_rs::fs::Resources::default();
565    /// resource.memory.attrs.insert("memory.numa_balancing".to_string(), "true".to_string());
566    /// // apply here
567    /// ```
568    pub attrs: HashMap<String, String>,
569}
570
571/// Resources limits on the number of processes.
572#[derive(Debug, Clone, Eq, PartialEq, Default)]
573#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
574pub struct PidResources {
575    /// The maximum number of processes that can exist in the control group.
576    ///
577    /// Note that attaching processes to the control group will still succeed _even_ if the limit
578    /// would be violated, however forks/clones inside the control group will have with `EAGAIN` if
579    /// they would violate the limit set here.
580    pub maximum_number_of_processes: Option<MaxValue>,
581}
582
583/// Resources limits about how the tasks can use the CPU.
584#[derive(Debug, Clone, Eq, PartialEq, Default)]
585#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
586pub struct CpuResources {
587    // cpuset
588    /// A comma-separated list of CPU IDs where the task in the control group can run. Dashes
589    /// between numbers indicate ranges.
590    pub cpus: Option<String>,
591    /// Same syntax as the `cpus` field of this structure, but applies to memory nodes instead of
592    /// processors.
593    pub mems: Option<String>,
594    // cpu
595    /// Weight of how much of the total CPU time should this control group get. Note that this is
596    /// hierarchical, so this is weighted against the siblings of this control group.
597    pub shares: Option<u64>,
598    /// In one `period`, how much can the tasks run in microseconds.
599    pub quota: Option<i64>,
600    /// Period of time in microseconds.
601    pub period: Option<u64>,
602    /// This is currently a no-operation.
603    pub realtime_runtime: Option<i64>,
604    /// This is currently a no-operation.
605    pub realtime_period: Option<u64>,
606    /// Customized key-value attributes
607    /// # Usage:
608    /// ```
609    /// let resource = &mut cgroups_rs::fs::Resources::default();
610    /// resource.cpu.attrs.insert("cpu.cfs_init_buffer_us".to_string(), "10".to_string());
611    /// // apply here
612    /// ```
613    pub attrs: HashMap<String, String>,
614}
615
616/// A device resource that can be allowed or denied access to.
617#[derive(Debug, Clone, Eq, PartialEq, Default)]
618#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
619pub struct DeviceResource {
620    /// If true, access to the device is allowed, otherwise it's denied.
621    pub allow: bool,
622    /// `'c'` for character device, `'b'` for block device; or `'a'` for all devices.
623    pub devtype: crate::fs::devices::DeviceType,
624    /// The major number of the device.
625    pub major: i64,
626    /// The minor number of the device.
627    pub minor: i64,
628    /// Sequence of `'r'`, `'w'` or `'m'`, each denoting read, write or mknod permissions.
629    pub access: Vec<crate::fs::devices::DevicePermissions>,
630}
631
632/// Limit the usage of devices for the control group's tasks.
633#[derive(Debug, Clone, Eq, PartialEq, Default)]
634#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
635pub struct DeviceResources {
636    /// For each device in the list, the limits in the structure are applied.
637    pub devices: Vec<DeviceResource>,
638}
639
640/// Assigned priority for a network device.
641#[derive(Debug, Clone, Eq, PartialEq, Default)]
642#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
643pub struct NetworkPriority {
644    /// The name (as visible in `ifconfig`) of the interface.
645    pub name: String,
646    /// Assigned priority.
647    pub priority: u64,
648}
649
650/// Collections of limits and tags that can be imposed on packets emitted by the tasks in the
651/// control group.
652#[derive(Debug, Clone, Eq, PartialEq, Default)]
653#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
654pub struct NetworkResources {
655    /// The networking class identifier to attach to the packets.
656    ///
657    /// This can then later be used in iptables and such to have special rules.
658    pub class_id: Option<u64>,
659    /// Priority of the egress traffic for each interface.
660    pub priorities: Vec<NetworkPriority>,
661}
662
663/// A hugepage type and its consumption limit for the control group.
664#[derive(Debug, Clone, Eq, PartialEq, Default)]
665#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
666pub struct HugePageResource {
667    /// The size of the hugepage, i.e. `2MB`, `1GB`, etc.
668    pub size: String,
669    /// The amount of bytes (of memory consumed by the tasks) that are allowed to be backed by
670    /// hugepages.
671    pub limit: u64,
672}
673
674/// Provides the ability to set consumption limit on each type of hugepages.
675#[derive(Debug, Clone, Eq, PartialEq, Default)]
676#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
677pub struct HugePageResources {
678    /// Set a limit of consumption for each hugepages type.
679    pub limits: Vec<HugePageResource>,
680}
681
682/// Weight for a particular block device.
683#[derive(Debug, Clone, Eq, PartialEq, Default)]
684#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
685pub struct BlkIoDeviceResource {
686    /// The major number of the device.
687    pub major: u64,
688    /// The minor number of the device.
689    pub minor: u64,
690    /// The weight of the device against the descendant nodes.
691    pub weight: Option<u16>,
692    /// The weight of the device against the sibling nodes.
693    pub leaf_weight: Option<u16>,
694}
695
696/// Provides the ability to throttle a device (both byte/sec, and IO op/s)
697#[derive(Debug, Clone, Eq, PartialEq, Default)]
698#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
699pub struct BlkIoDeviceThrottleResource {
700    /// The major number of the device.
701    pub major: u64,
702    /// The minor number of the device.
703    pub minor: u64,
704    /// The rate.
705    pub rate: u64,
706}
707
708/// General block I/O resource limits.
709#[derive(Debug, Clone, Eq, PartialEq, Default)]
710#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
711pub struct BlkIoResources {
712    /// The weight of the control group against descendant nodes.
713    pub weight: Option<u16>,
714    /// The weight of the control group against sibling nodes.
715    pub leaf_weight: Option<u16>,
716    /// For each device, a separate weight (both normal and leaf) can be provided.
717    pub weight_device: Vec<BlkIoDeviceResource>,
718    /// Throttled read bytes/second can be provided for each device.
719    pub throttle_read_bps_device: Vec<BlkIoDeviceThrottleResource>,
720    /// Throttled read IO operations per second can be provided for each device.
721    pub throttle_read_iops_device: Vec<BlkIoDeviceThrottleResource>,
722    /// Throttled written bytes/second can be provided for each device.
723    pub throttle_write_bps_device: Vec<BlkIoDeviceThrottleResource>,
724    /// Throttled write IO operations per second can be provided for each device.
725    pub throttle_write_iops_device: Vec<BlkIoDeviceThrottleResource>,
726
727    /// Customized key-value attributes
728    /// # Usage:
729    /// ```
730    /// let resource = &mut cgroups_rs::fs::Resources::default();
731    /// resource.blkio.attrs.insert("io.cost.weight".to_string(), "10".to_string());
732    /// // apply here
733    /// ```
734    pub attrs: HashMap<String, String>,
735}
736
737/// The resource limits and constraints that will be set on the control group.
738#[derive(Debug, Clone, Eq, PartialEq, Default)]
739#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
740pub struct Resources {
741    /// Memory usage related limits.
742    pub memory: MemoryResources,
743    /// Process identifier related limits.
744    pub pid: PidResources,
745    /// CPU related limits.
746    pub cpu: CpuResources,
747    /// Device related limits.
748    pub devices: DeviceResources,
749    /// Network related tags and limits.
750    pub network: NetworkResources,
751    /// Hugepages consumption related limits.
752    pub hugepages: HugePageResources,
753    /// Block device I/O related limits.
754    pub blkio: BlkIoResources,
755}
756
757impl Subsystem {
758    fn enter(self, path: &Path) -> Self {
759        match self {
760            Subsystem::Pid(mut cont) => Subsystem::Pid({
761                cont.get_path_mut().push(path);
762                cont
763            }),
764            Subsystem::Mem(mut cont) => Subsystem::Mem({
765                cont.get_path_mut().push(path);
766                cont
767            }),
768            Subsystem::CpuSet(mut cont) => Subsystem::CpuSet({
769                cont.get_path_mut().push(path);
770                cont
771            }),
772            Subsystem::CpuAcct(mut cont) => Subsystem::CpuAcct({
773                cont.get_path_mut().push(path);
774                cont
775            }),
776            Subsystem::Cpu(mut cont) => Subsystem::Cpu({
777                cont.get_path_mut().push(path);
778                cont
779            }),
780            Subsystem::Devices(mut cont) => Subsystem::Devices({
781                cont.get_path_mut().push(path);
782                cont
783            }),
784            Subsystem::Freezer(mut cont) => Subsystem::Freezer({
785                cont.get_path_mut().push(path);
786                cont
787            }),
788            Subsystem::NetCls(mut cont) => Subsystem::NetCls({
789                cont.get_path_mut().push(path);
790                cont
791            }),
792            Subsystem::BlkIo(mut cont) => Subsystem::BlkIo({
793                cont.get_path_mut().push(path);
794                cont
795            }),
796            Subsystem::PerfEvent(mut cont) => Subsystem::PerfEvent({
797                cont.get_path_mut().push(path);
798                cont
799            }),
800            Subsystem::NetPrio(mut cont) => Subsystem::NetPrio({
801                cont.get_path_mut().push(path);
802                cont
803            }),
804            Subsystem::HugeTlb(mut cont) => Subsystem::HugeTlb({
805                cont.get_path_mut().push(path);
806                cont
807            }),
808            Subsystem::Rdma(mut cont) => Subsystem::Rdma({
809                cont.get_path_mut().push(path);
810                cont
811            }),
812            Subsystem::Systemd(mut cont) => Subsystem::Systemd({
813                cont.get_path_mut().push(path);
814                cont
815            }),
816        }
817    }
818
819    pub fn to_controller(&self) -> &dyn Controller {
820        match self {
821            Subsystem::Pid(cont) => cont,
822            Subsystem::Mem(cont) => cont,
823            Subsystem::CpuSet(cont) => cont,
824            Subsystem::CpuAcct(cont) => cont,
825            Subsystem::Cpu(cont) => cont,
826            Subsystem::Devices(cont) => cont,
827            Subsystem::Freezer(cont) => cont,
828            Subsystem::NetCls(cont) => cont,
829            Subsystem::BlkIo(cont) => cont,
830            Subsystem::PerfEvent(cont) => cont,
831            Subsystem::NetPrio(cont) => cont,
832            Subsystem::HugeTlb(cont) => cont,
833            Subsystem::Rdma(cont) => cont,
834            Subsystem::Systemd(cont) => cont,
835        }
836    }
837
838    pub fn controller_name(&self) -> String {
839        self.to_controller().control_type().to_string()
840    }
841}
842
843/// The values for `memory.hight` or `pids.max`
844#[derive(Eq, PartialEq, Copy, Clone, Debug)]
845#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
846pub enum MaxValue {
847    /// This value is returned when the text is `"max"`.
848    Max,
849    /// When the value is a numerical value, they are returned via this enum field.
850    Value(i64),
851}
852
853#[allow(clippy::derivable_impls)]
854impl Default for MaxValue {
855    fn default() -> Self {
856        MaxValue::Max
857    }
858}
859
860impl MaxValue {
861    #[allow(clippy::should_implement_trait, clippy::wrong_self_convention)]
862    fn to_i64(&self) -> i64 {
863        match self {
864            MaxValue::Max => -1,
865            MaxValue::Value(num) => *num,
866        }
867    }
868}
869
870impl fmt::Display for MaxValue {
871    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
872        match self {
873            MaxValue::Max => write!(f, "max"),
874            MaxValue::Value(num) => write!(f, "{}", num),
875        }
876    }
877}
878
879pub fn parse_max_value(s: &str) -> Result<MaxValue> {
880    if s.trim() == "max" {
881        return Ok(MaxValue::Max);
882    }
883    match s.trim().parse() {
884        Ok(val) => Ok(MaxValue::Value(val)),
885        Err(e) => Err(Error::with_cause(ParseError, e)),
886    }
887}
888
889// Flat keyed
890//  KEY0 VAL0\n
891//  KEY1 VAL1\n
892pub fn flat_keyed_to_vec(mut file: File) -> Result<Vec<(String, i64)>> {
893    let mut content = String::new();
894    file.read_to_string(&mut content)
895        .map_err(|e| Error::with_cause(ReadFailed("FIXME: read_string_from".to_string()), e))?;
896
897    let mut v = Vec::new();
898    for line in content.lines() {
899        let parts: Vec<&str> = line.split(' ').collect();
900        if parts.len() == 2 {
901            if let Ok(i) = parts[1].parse::<i64>() {
902                v.push((parts[0].to_string(), i));
903            }
904        }
905    }
906    Ok(v)
907}
908
909// Flat keyed
910//  KEY0 VAL0\n
911//  KEY1 VAL1\n
912pub fn flat_keyed_to_hashmap(mut file: File) -> Result<HashMap<String, i64>> {
913    let mut content = String::new();
914    file.read_to_string(&mut content)
915        .map_err(|e| Error::with_cause(ReadFailed("FIXME: read_string_from".to_string()), e))?;
916
917    let mut h = HashMap::new();
918    for line in content.lines() {
919        let parts: Vec<&str> = line.split(' ').collect();
920        if parts.len() == 2 {
921            if let Ok(i) = parts[1].parse::<i64>() {
922                h.insert(parts[0].to_string(), i);
923            }
924        }
925    }
926    Ok(h)
927}
928
929// Nested keyed
930//  KEY0 SUB_KEY0=VAL00 SUB_KEY1=VAL01...
931//  KEY1 SUB_KEY0=VAL10 SUB_KEY1=VAL11...
932pub fn nested_keyed_to_hashmap(mut file: File) -> Result<HashMap<String, HashMap<String, i64>>> {
933    let mut content = String::new();
934    file.read_to_string(&mut content)
935        .map_err(|e| Error::with_cause(ReadFailed("FIXME: read_string_from".to_string()), e))?;
936
937    let mut h = HashMap::new();
938    for line in content.lines() {
939        let parts: Vec<&str> = line.split(' ').collect();
940        if parts.is_empty() {
941            continue;
942        }
943        let mut th = HashMap::new();
944        for item in parts[1..].iter() {
945            let fields: Vec<&str> = item.split('=').collect();
946            if fields.len() == 2 {
947                if let Ok(i) = fields[1].parse::<i64>() {
948                    th.insert(fields[0].to_string(), i);
949                }
950            }
951        }
952        h.insert(parts[0].to_string(), th);
953    }
954
955    Ok(h)
956}
957
958fn read_from<T>(mut file: File) -> Result<T>
959where
960    T: FromStr,
961    <T as FromStr>::Err: 'static + Send + Sync + std::error::Error,
962{
963    let mut string = String::new();
964    match file.read_to_string(&mut string) {
965        Ok(_) => string
966            .trim()
967            .parse::<T>()
968            .map_err(|e| Error::with_cause(ParseError, e)),
969        Err(e) => Err(Error::with_cause(
970            ReadFailed("FIXME: can't get path in fn read_from".to_string()),
971            e,
972        )),
973    }
974}
975
976fn read_string_from(mut file: File) -> Result<String> {
977    let mut string = String::new();
978    match file.read_to_string(&mut string) {
979        Ok(_) => Ok(string.trim().to_string()),
980        Err(e) => Err(Error::with_cause(
981            ReadFailed("FIXME: can't get path in fn read_string_from".to_string()),
982            e,
983        )),
984    }
985}
986
987/// read and parse an u64 data
988fn read_u64_from(file: File) -> Result<u64> {
989    read_from::<u64>(file)
990}
991
992/// read and parse an i64 data
993fn read_i64_from(file: File) -> Result<i64> {
994    read_from::<i64>(file)
995}