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