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