Compare commits

..

22 Commits
v0.3.4 ... v0.3

Author SHA1 Message Date
Fabiano Fidêncio
6e273cd2fb Merge pull request #149 from kata-containers/bump-rust
Bump Rust version to 1.85.1
2025-07-09 09:37:32 +02:00
Xuewei Niu
bce4b0bb3b Fix the warning about config file
The cargo complaints that ".cargo/config" is deprecated in favor of
".cargo/config.toml". Therefore, the file is renamed to it to make cargo
happy.

Signed-off-by: Xuewei Niu <niuxuewei.nxw@antgroup.com>
2025-07-08 23:56:57 +08:00
Xuewei Niu
f5a67c632e Fix the issues that makes cargo clippy unhappy
Most of the issues are small, but `fold()`. The usage of `fold()` in these
cases confuses me a lot, so that I don't follow the sugguestions from
clippy to replace `fold()` with `try_fold()`. Instead, I replace them with
`map() + collect()`.

Signed-off-by: Xuewei Niu <niuxuewei.nxw@antgroup.com>
2025-07-08 23:56:55 +08:00
Xuewei Niu
c8febbc67d .github: Bump Rust version to 1.85.1
Rust 1.69.0, released in 2023, lags significantly behind the version used
by Kata Containers. Therefore, we bump it to 1.85.1.

Signed-off-by: Xuewei Niu <niuxuewei.nxw@antgroup.com>
2025-07-08 17:07:52 +08:00
Fupan Li
2536e33696 Merge pull request #143 from oguzbilgener/no-regex
refactor: Parse HugeTLB size without Regex
2025-06-12 10:23:34 +08:00
Oguz Bilgener
694627e2fc refactor: Parse HugeTLB size without Regex
This removes the only place the regex crate is used, which could lead to significant binary size savings in projects that don't use the regex craate.

Signed-off-by: Oguz Bilgener <oguz@bilgener.me>
2025-05-29 11:05:53 -04:00
Fupan Li
de9625ff57 Merge pull request #146 from fidencio/topic/release-v0.3.5
release: v0.3.5
2025-05-29 16:33:54 +08:00
Fupan Li
b6b65f79d1 Merge pull request #141 from michelhe/remove-outdated-docs
hierarchies: Remove outdated documentation
2025-05-29 16:31:55 +08:00
Fupan Li
547fb08c03 Merge pull request #140 from jokemanfire/dev
fix bug: if cgroup path has ":"
2025-05-29 16:31:14 +08:00
Fupan Li
ec9f3547ed Merge pull request #135 from zzzzzzzzzy9/pull-branch
rdma: read rdma.max file
2025-05-29 16:30:09 +08:00
Michel Heily
82a6aa491a hierarchies: Remove outdated documentation
The documentation in the hierarchies module is outdated, this project
does support cgroups2 and the unified hierarchy :)

Signed-off-by: Michel Heily <michelheily@gmail.com>
2025-05-29 10:21:27 +02:00
zzzzzzzzzy9
65c36214b7 rdma: read rdma.max file
This is a simple one, just support rdma.max.

Signed-off-by: zzzzzzzzzy9 <zhang.yu58@zte.com.cn>
Signed-off-by: Fabiano Fidêncio <fidencio@northflank.com>
2025-05-29 10:12:10 +02:00
Fabiano Fidêncio
e0d0b8f4bc release: v0.3.5
There are several fixes since the last release, and itś been a few years
waiting for a new one.

Signed-off-by: Fabiano Fidêncio <fidencio@northflank.com>
2025-05-29 08:53:03 +02:00
Fabiano Fidêncio
db822470e5 Merge pull request #116 from xueruini/fix-mem-panic
Use default memory stats instead of panic.
2025-05-29 08:14:51 +02:00
Fabiano Fidêncio
362373b3ec memory: Fix test_procs_iterator_cgroup test
Otherwise it simply breaks.

Signed-off-by: Alex Man <alexman@stripe.com>
Signed-off-by: Fabiano Fidêncio <fidencio@northflank.com>
2025-05-28 23:02:04 +02:00
Ruini Xue
eadbf53140 memory: Use default memory stats instead of panic.
When containers are terminated, cgroup v2 memory metrics under
/sys/fs/cgroup may disappear.  Previously, kata-agent assumed these
metrics always exist, leading to panics as reported in
kata-containers#138.

This commit returns default value (0) when memory metric files are
missing.  This behaviour aligns with cgroup v1, which also defaults to 0
memory metric files are missing:
- Memory.limit_in_bytes which maps to m.max
  https://github.com/kata-containers/cgroups-rs/blob/main/src/memory.rs#L635
- Memory.soft_limit_in_bytes which maps to m.low
  https://github.com/kata-containers/cgroups-rs/blob/main/src/memory.rs#L661
- MemSwap.fail_cnt:
  https://github.com/kata-containers/cgroups-rs/blob/main/src/memory.rs#L631

Signed-off-by: Ruini Xue <ruini.xue@gmail.com>
Signed-off-by: Alex Man <alexman@stripe.com>
Signed-off-by: Fabiano Fidêncio <fidencio@northflank.com>
2025-05-28 23:02:04 +02:00
Fabiano Fidêncio
b3c57840ee lib: Ignore dead_code warning
I'm not fmiliar with this crate, so let's just ignore the warning for
now instead of removing the code.

Signed-off-by: Fabiano Fidêncio <fidencio@northflank.com>
2025-05-28 23:02:04 +02:00
Fabiano Fidêncio
b10e52d85f Merge pull request #133 from nxfs/controllers-parsing
controllers parsing is resilient to trailing newline
2025-05-28 22:44:03 +02:00
jokemanfire
7d4d4579a3 fix bug: if cgroup path has ":"
This bug is occur in cgroup path "which has :"

Signed-off-by: jokemanfire <hu.dingyang@zte.com.cn>
2024-11-02 20:23:48 +08:00
Tim Zhang
eb3e37a4bc Merge pull request #132 from aa624545345/collect-cgroup-path
Correct path of cgroup when running a container in a container
2024-05-13 17:38:52 +08:00
乔琛 10307740
4005ad844d cgroup: collect path of cgroup when running a container in a container
Path of cgroup is wrong when running a container in a container. Use
the root path of mountinfo fetched from /proc/$(shim_pid)/mountinfo
to trim the path obtained from /proc/self/mountinfo.

Fixes: #131

Signed-off-by: 乔琛 10307740 <qiao.chen@zte.com.cn>
2024-04-16 16:40:05 +08:00
Fernand Sieber
ef3497646f controllers parsing is resilient to trailing newline
In older kernel versions (tested with 5.10.208), the cgroup.controllers
files ends with a newline. This newline is not trimmed and as such the
application later can incorrectly consider that the last controller is not
supported
2024-03-20 13:55:56 +02:00
20 changed files with 349 additions and 221 deletions

View File

@@ -1,7 +1,7 @@
name: BVT
on: [pull_request]
env:
RUST_VERSION: 1.69.0
RUST_VERSION: 1.85.1
jobs:
build:
name: Build

View File

@@ -5,7 +5,7 @@ repository = "https://github.com/kata-containers/cgroups-rs"
keywords = ["linux", "cgroup", "containers", "isolation"]
categories = ["os", "api-bindings", "os::unix-apis"]
license = "MIT OR Apache-2.0"
version = "0.3.4"
version = "0.3.5"
authors = ["The Kata Containers community <kata-dev@lists.katacontainers.io>", "Levente Kurusa <lkurusa@acm.org>", "Sam Wilson <tecywiz121@hotmail.com>"]
edition = "2018"
homepage = "https://github.com/kata-containers/cgroups-rs"
@@ -13,7 +13,6 @@ readme = "README.md"
[dependencies]
log = "0.4"
regex = "1.1"
nix = { version = "0.25.0", default-features = false, features = ["event", "fs", "process"] }
libc = "0.2"
serde = { version = "1.0", features = ["derive"], optional = true }

View File

@@ -181,13 +181,14 @@ fn parse_io_stat(s: String) -> Vec<IoStat> {
fn parse_io_service_total(s: String) -> Result<u64> {
s.lines()
.filter(|x| x.split_whitespace().count() == 2)
.fold(Err(Error::new(ParseError)), |_, x| {
match x.split_whitespace().collect::<Vec<_>>().as_slice() {
["Total", val] => val.parse::<u64>().map_err(|_| Error::new(ParseError)),
_ => Err(Error::new(ParseError)),
.find_map(|line| {
let mut parts = line.split_whitespace();
match (parts.next(), parts.next(), parts.next()) {
(Some("Total"), Some(val), None) => val.parse::<u64>().ok(),
_ => None,
}
})
.ok_or_else(|| Error::new(ParseError))
}
fn parse_blkio_data(s: String) -> Result<Vec<BlkIoData>> {
@@ -430,10 +431,10 @@ impl<'a> From<&'a Subsystem> for &'a BlkIoController {
impl BlkIoController {
/// Constructs a new `BlkIoController` with `root` serving as the root of the control group.
pub fn new(root: PathBuf, v2: bool) -> Self {
pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self {
Self {
base: root.clone(),
path: root,
base: root,
path: point,
v2,
}
}

View File

@@ -9,6 +9,7 @@
use crate::error::ErrorKind::*;
use crate::error::*;
use crate::hierarchies::V1;
use crate::{CgroupPid, ControllIdentifier, Controller, Hierarchy, Resources, Subsystem};
use std::collections::HashMap;
@@ -521,7 +522,7 @@ fn supported_controllers() -> Vec<String> {
let ret = fs::read_to_string(p.as_str());
ret.unwrap_or_default()
.split(' ')
.map(|x| x.to_string())
.map(|x| x.trim().to_string())
.collect::<Vec<String>>()
}
@@ -588,19 +589,47 @@ pub fn get_cgroups_relative_paths_by_pid(pid: u32) -> Result<HashMap<String, Str
get_cgroups_relative_paths_by_path(path)
}
fn get_cgroup_destination(mut mount_root: String, pidpath: String) -> String {
if mount_root == "/" {
mount_root = String::from("");
}
pidpath.trim_start_matches(&mount_root).to_string()
}
pub fn existing_path(paths: HashMap<String, String>) -> Result<HashMap<String, String>> {
let mount_roots_v1 = V1::new();
let mut mount_roots_subsystems_map = HashMap::new();
for s in mount_roots_v1.subsystems().iter() {
let controller_name = s.controller_name();
let path_from_cgroup = paths
.get(&controller_name)
.ok_or(Error::new(Common(format!(
"controller {} found in mountinfo, but not found in cgroup.",
controller_name
))))?;
let path_from_mountinfo = s.to_controller().base().to_string_lossy().to_string();
let des_path = get_cgroup_destination(path_from_mountinfo, path_from_cgroup.to_owned());
mount_roots_subsystems_map.insert(controller_name, des_path);
}
Ok(mount_roots_subsystems_map)
}
fn get_cgroups_relative_paths_by_path(path: String) -> Result<HashMap<String, String>> {
let mut m = HashMap::new();
let content =
fs::read_to_string(path.clone()).map_err(|e| Error::with_cause(ReadFailed(path), e))?;
for l in content.lines() {
let fl: Vec<&str> = l.split(':').collect();
if fl.len() != 3 {
continue;
}
let keys: Vec<&str> = fl[1].split(',').collect();
for key in &keys {
m.insert(key.to_string(), fl[2].to_string());
// cgroup path may have ":" , likes
// "2:cpu,cpuacct:/system.slice/containerd.service/test.slice:cri-containerd:96b37a2edf84351487f42039e137427f1812f678850675fac214caf597ee5e4a"
for line in content.lines() {
if let Some((first_value_part, remaining_path)) =
line.split_once(':').unwrap_or_default().1.split_once(':')
{
let keys: Vec<&str> = first_value_part.split(',').collect();
keys.iter().for_each(|key| {
m.insert(key.to_string(), remaining_path.to_string());
});
}
}
Ok(m)

View File

@@ -59,7 +59,6 @@ impl ControllerInternal for CpuController {
fn get_path(&self) -> &PathBuf {
&self.path
}
fn get_path_mut(&mut self) -> &mut PathBuf {
&mut self.path
}
@@ -113,10 +112,10 @@ impl<'a> From<&'a Subsystem> for &'a CpuController {
impl CpuController {
/// Contructs a new `CpuController` with `root` serving as the root of the control group.
pub fn new(root: PathBuf, v2: bool) -> Self {
pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self {
Self {
base: root.clone(),
path: root,
base: root,
path: point,
v2,
}
}

View File

@@ -100,10 +100,10 @@ impl<'a> From<&'a Subsystem> for &'a CpuAcctController {
impl CpuAcctController {
/// Contructs a new `CpuAcctController` with `root` serving as the root of the control group.
pub fn new(root: PathBuf) -> Self {
pub fn new(point: PathBuf, root: PathBuf) -> Self {
Self {
base: root.clone(),
path: root,
base: root,
path: point,
}
}

View File

@@ -254,10 +254,10 @@ fn parse_range(s: String) -> Result<Vec<(u64, u64)>> {
impl CpuSetController {
/// Contructs a new `CpuSetController` with `root` serving as the root of the control group.
pub fn new(root: PathBuf, v2: bool) -> Self {
pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self {
Self {
base: root.clone(),
path: root,
base: root,
path: point,
v2,
}
}
@@ -602,7 +602,7 @@ mod tests {
"1,2,3,4".to_string(),
"1-5,6-7,8-9".to_string(),
];
let expecteds = vec![
let expecteds = [
vec![(1, 1), (2, 2), (4, 6), (9, 9)],
vec![],
vec![(1, 1)],

View File

@@ -204,10 +204,10 @@ impl<'a> From<&'a Subsystem> for &'a DevicesController {
impl DevicesController {
/// Constructs a new `DevicesController` with `root` serving as the root of the control group.
pub fn new(root: PathBuf) -> Self {
pub fn new(point: PathBuf, root: PathBuf) -> Self {
Self {
base: root.clone(),
path: root,
base: root,
path: point,
}
}
@@ -295,43 +295,48 @@ impl DevicesController {
let mut s = String::new();
let res = file.read_to_string(&mut s);
match res {
Ok(_) => {
s.lines().fold(Ok(Vec::new()), |acc, line| {
let ls = line.to_string().split(|c| c == ' ' || c == ':').map(|x| x.to_string()).collect::<Vec<String>>();
if acc.is_err() || ls.len() != 4 {
error!("allowed_devices: acc: {:?}, ls: {:?}", acc, ls);
Err(Error::new(ParseError))
} else {
let devtype = DeviceType::from_char(ls[0].chars().next());
let mut major = ls[1].parse::<i64>();
let mut minor = ls[2].parse::<i64>();
if major.is_err() && ls[1] == "*" {
major = Ok(-1);
}
if minor.is_err() && ls[2] == "*" {
minor = Ok(-1);
}
if devtype.is_none() || major.is_err() || minor.is_err() || !DevicePermissions::is_valid(&ls[3]) {
error!("allowed_devices: acc: {:?}, ls: {:?}, devtype: {:?}, major {:?} minor {:?} ls3 {:?}",
acc, ls, devtype, major, minor, &ls[3]);
Err(Error::new(ParseError))
} else {
let access = DevicePermissions::from_str(&ls[3])?;
let mut acc = acc.unwrap();
acc.push(DeviceResource {
allow: true,
devtype: devtype.unwrap(),
major: major.unwrap(),
minor: minor.unwrap(),
access,
});
Ok(acc)
}
}
})
},
Ok(_) => s
.lines()
.map(|line| parse_device_line(line, true))
.collect(),
Err(e) => Err(Error::with_cause(ReadFailed("devices.list".to_string()), e)),
}
})
}
}
fn parse_device_number(s: &str) -> Result<i64> {
if s == "*" {
Ok(-1)
} else {
s.parse::<i64>().map_err(|_| Error::new(ParseError))
}
}
fn parse_device_line(line: &str, allow: bool) -> Result<DeviceResource> {
let parts: Vec<&str> = line.split([' ', ':']).collect();
if parts.len() != 4 {
error!("allowed_devices: invalid line format: {:?}", line);
return Err(Error::new(ParseError));
}
let devtype = DeviceType::from_char(parts[0].chars().next()).ok_or_else(|| {
error!("allowed_devices: invalid device type: {:?}", parts[0]);
Error::new(ParseError)
})?;
let major = parse_device_number(parts[1]).inspect_err(|_| {
error!("allowed_devices: invalid major number: {:?}", parts[1]);
})?;
let minor = parse_device_number(parts[2]).inspect_err(|_| {
error!("allowed_devices: invalid minor number: {:?}", parts[2]);
})?;
let access = DevicePermissions::from_str(parts[3])?;
Ok(DeviceResource {
allow,
devtype,
major,
minor,
access,
})
}

View File

@@ -84,14 +84,13 @@ impl<'a> From<&'a Subsystem> for &'a FreezerController {
impl FreezerController {
/// Contructs a new `FreezerController` with `root` serving as the root of the control group.
pub fn new(root: PathBuf, v2: bool) -> Self {
pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self {
Self {
base: root.clone(),
path: root,
base: root,
path: point,
v2,
}
}
/// Freezes the processes in the control group.
pub fn freeze(&self) -> Result<()> {
let mut file_name = "freezer.state";

View File

@@ -5,9 +5,6 @@
//
//! This module represents the various control group hierarchies the Linux kernel supports.
//!
//! Currently, we only support the cgroupv1 hierarchy, but in the future we will add support for
//! the Unified Hierarchy.
use std::fs;
use std::fs::File;
@@ -37,6 +34,8 @@ use crate::cgroup::Cgroup;
/// See `proc(5)` for format details.
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Mountinfo {
/// Mount root directory of the file system.
pub mount_root: PathBuf,
/// Mount pathname relative to the process's root.
pub mount_point: PathBuf,
/// Filesystem type (main type with optional sub-type).
@@ -57,6 +56,7 @@ pub(crate) fn parse_mountinfo_for_line(line: &str) -> Option<Mountinfo> {
return None;
}
let mount_point = PathBuf::from(s0_values[4]);
let mount_root = PathBuf::from(s0_values[3]);
let fs_type_values: Vec<_> = s1_values[0].trim().split('.').collect();
let fs_type = match fs_type_values.len() {
1 => (fs_type_values[0].to_string(), None),
@@ -69,6 +69,7 @@ pub(crate) fn parse_mountinfo_for_line(line: &str) -> Option<Mountinfo> {
let super_opts: Vec<String> = s1_values[2].trim().split(',').map(String::from).collect();
Some(Mountinfo {
mount_root,
mount_point,
fs_type,
super_opts,
@@ -123,47 +124,53 @@ impl Hierarchy for V1 {
// The cgroup writeback feature requires cooperation between memcgs and blkcgs
// To avoid exceptions, we should add_task for blkcg before memcg(push BlkIo before Mem)
// For more Information: https://www.alibabacloud.com/help/doc-detail/155509.htm
if let Some(root) = self.get_mount_point(Controllers::BlkIo) {
subs.push(Subsystem::BlkIo(BlkIoController::new(root, false)));
if let Some((point, root)) = self.get_mount_point(Controllers::BlkIo) {
subs.push(Subsystem::BlkIo(BlkIoController::new(point, root, false)));
}
if let Some(root) = self.get_mount_point(Controllers::Mem) {
subs.push(Subsystem::Mem(MemController::new(root, false)));
if let Some((point, root)) = self.get_mount_point(Controllers::Mem) {
subs.push(Subsystem::Mem(MemController::new(point, root, false)));
}
if let Some(root) = self.get_mount_point(Controllers::Pids) {
subs.push(Subsystem::Pid(PidController::new(root, false)));
if let Some((point, root)) = self.get_mount_point(Controllers::Pids) {
subs.push(Subsystem::Pid(PidController::new(point, root, false)));
}
if let Some(root) = self.get_mount_point(Controllers::CpuSet) {
subs.push(Subsystem::CpuSet(CpuSetController::new(root, false)));
if let Some((point, root)) = self.get_mount_point(Controllers::CpuSet) {
subs.push(Subsystem::CpuSet(CpuSetController::new(point, root, false)));
}
if let Some(root) = self.get_mount_point(Controllers::CpuAcct) {
subs.push(Subsystem::CpuAcct(CpuAcctController::new(root)));
if let Some((point, root)) = self.get_mount_point(Controllers::CpuAcct) {
subs.push(Subsystem::CpuAcct(CpuAcctController::new(point, root)));
}
if let Some(root) = self.get_mount_point(Controllers::Cpu) {
subs.push(Subsystem::Cpu(CpuController::new(root, false)));
if let Some((point, root)) = self.get_mount_point(Controllers::Cpu) {
subs.push(Subsystem::Cpu(CpuController::new(point, root, false)));
}
if let Some(root) = self.get_mount_point(Controllers::Devices) {
subs.push(Subsystem::Devices(DevicesController::new(root)));
if let Some((point, root)) = self.get_mount_point(Controllers::Devices) {
subs.push(Subsystem::Devices(DevicesController::new(point, root)));
}
if let Some(root) = self.get_mount_point(Controllers::Freezer) {
subs.push(Subsystem::Freezer(FreezerController::new(root, false)));
if let Some((point, root)) = self.get_mount_point(Controllers::Freezer) {
subs.push(Subsystem::Freezer(FreezerController::new(
point, root, false,
)));
}
if let Some(root) = self.get_mount_point(Controllers::NetCls) {
subs.push(Subsystem::NetCls(NetClsController::new(root)));
if let Some((point, root)) = self.get_mount_point(Controllers::NetCls) {
subs.push(Subsystem::NetCls(NetClsController::new(point, root)));
}
if let Some(root) = self.get_mount_point(Controllers::PerfEvent) {
subs.push(Subsystem::PerfEvent(PerfEventController::new(root)));
if let Some((point, root)) = self.get_mount_point(Controllers::PerfEvent) {
subs.push(Subsystem::PerfEvent(PerfEventController::new(point, root)));
}
if let Some(root) = self.get_mount_point(Controllers::NetPrio) {
subs.push(Subsystem::NetPrio(NetPrioController::new(root)));
if let Some((point, root)) = self.get_mount_point(Controllers::NetPrio) {
subs.push(Subsystem::NetPrio(NetPrioController::new(point, root)));
}
if let Some(root) = self.get_mount_point(Controllers::HugeTlb) {
subs.push(Subsystem::HugeTlb(HugeTlbController::new(root, false)));
if let Some((point, root)) = self.get_mount_point(Controllers::HugeTlb) {
subs.push(Subsystem::HugeTlb(HugeTlbController::new(
point, root, false,
)));
}
if let Some(root) = self.get_mount_point(Controllers::Rdma) {
subs.push(Subsystem::Rdma(RdmaController::new(root)));
if let Some((point, root)) = self.get_mount_point(Controllers::Rdma) {
subs.push(Subsystem::Rdma(RdmaController::new(point, root)));
}
if let Some(root) = self.get_mount_point(Controllers::Systemd) {
subs.push(Subsystem::Systemd(SystemdController::new(root, false)));
if let Some((point, root)) = self.get_mount_point(Controllers::Systemd) {
subs.push(Subsystem::Systemd(SystemdController::new(
point, root, false,
)));
}
subs
@@ -218,29 +225,51 @@ impl Hierarchy for V2 {
for s in controller_list {
match s {
"cpu" => {
subs.push(Subsystem::Cpu(CpuController::new(self.root(), true)));
subs.push(Subsystem::Cpu(CpuController::new(
self.root(),
PathBuf::from(""),
true,
)));
}
"io" => {
subs.push(Subsystem::BlkIo(BlkIoController::new(self.root(), true)));
subs.push(Subsystem::BlkIo(BlkIoController::new(
self.root(),
PathBuf::from(""),
true,
)));
}
"cpuset" => {
subs.push(Subsystem::CpuSet(CpuSetController::new(self.root(), true)));
subs.push(Subsystem::CpuSet(CpuSetController::new(
self.root(),
PathBuf::from(""),
true,
)));
}
"memory" => {
subs.push(Subsystem::Mem(MemController::new(self.root(), true)));
subs.push(Subsystem::Mem(MemController::new(
self.root(),
PathBuf::from(""),
true,
)));
}
"pids" => {
subs.push(Subsystem::Pid(PidController::new(self.root(), true)));
subs.push(Subsystem::Pid(PidController::new(
self.root(),
PathBuf::from(""),
true,
)));
}
"freezer" => {
subs.push(Subsystem::Freezer(FreezerController::new(
self.root(),
PathBuf::from(""),
true,
)));
}
"hugetlb" => {
subs.push(Subsystem::HugeTlb(HugeTlbController::new(
self.root(),
PathBuf::from(""),
true,
)));
}
@@ -275,10 +304,10 @@ impl V1 {
}
}
pub fn get_mount_point(&self, controller: Controllers) -> Option<PathBuf> {
pub fn get_mount_point(&self, controller: Controllers) -> Option<(PathBuf, PathBuf)> {
self.mountinfo.iter().find_map(|m| {
if m.fs_type.0 == "cgroup" && m.super_opts.contains(&controller.to_string()) {
return Some(m.mount_point.clone());
return Some((m.mount_point.to_owned(), m.mount_root.to_owned()));
}
None
})
@@ -337,19 +366,19 @@ mod tests {
fn test_parse_mount() {
let mountinfo = vec![
("29 26 0:26 / /sys/fs/cgroup/cpuset,cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:10 - cgroup cgroup rw,cpuset,cpu,cpuacct",
Mountinfo{mount_point: PathBuf::from("/sys/fs/cgroup/cpuset,cpu,cpuacct"), fs_type: ("cgroup".to_string(), None), super_opts: vec![
Mountinfo{mount_root: PathBuf::from("/"), mount_point: PathBuf::from("/sys/fs/cgroup/cpuset,cpu,cpuacct"), fs_type: ("cgroup".to_string(), None), super_opts: vec![
"rw".to_string(),
"cpuset".to_string(),
"cpu".to_string(),
"cpuacct".to_string(),
]}),
("121 1731 0:42 / /shm rw,nosuid,nodev,noexec,relatime shared:68 master:66 - tmpfs shm rw,size=65536k",
Mountinfo{mount_point: PathBuf::from("/shm"), fs_type: ("tmpfs".to_string(), None), super_opts: vec![
Mountinfo{mount_root: PathBuf::from("/"), mount_point: PathBuf::from("/shm"), fs_type: ("tmpfs".to_string(), None), super_opts: vec![
"rw".to_string(),
"size=65536k".to_string(),
]}),
("121 1731 0:42 / /shm rw,nosuid,nodev,noexec,relatime shared:68 master:66 - tmpfs.123 shm rw,size=65536k",
Mountinfo{mount_point: PathBuf::from("/shm"), fs_type: ("tmpfs".to_string(), Some("123".to_string())), super_opts: vec![
Mountinfo{mount_root: PathBuf::from("/"), mount_point: PathBuf::from("/shm"), fs_type: ("tmpfs".to_string(), Some("123".to_string())), super_opts: vec![
"rw".to_string(),
"size=65536k".to_string(),
]}),

View File

@@ -88,11 +88,11 @@ impl<'a> From<&'a Subsystem> for &'a HugeTlbController {
impl HugeTlbController {
/// Constructs a new `HugeTlbController` with `root` serving as the root of the control group.
pub fn new(root: PathBuf, v2: bool) -> Self {
pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self {
let sizes = get_hugepage_sizes();
Self {
base: root.clone(),
path: root,
base: root,
path: point,
sizes,
v2,
}
@@ -181,7 +181,6 @@ impl HugeTlbController {
}
pub const HUGEPAGESIZE_DIR: &str = "/sys/kernel/mm/hugepages";
use regex::Regex;
use std::collections::HashMap;
use std::fs;
@@ -263,37 +262,46 @@ pub fn get_decimal_abbrs() -> Vec<String> {
}
fn parse_size(s: &str, m: &HashMap<String, u128>) -> Result<u128> {
let re = Regex::new(r"(?P<num>\d+)(?P<mul>[kKmMgGtTpP]?)[bB]?$");
// Remove leading/trailing whitespace.
let s = s.trim();
if re.is_err() {
// Remove an optional trailing 'b' or 'B'
let s = if let Some(stripped) = s.strip_suffix('b').or_else(|| s.strip_suffix('B')) {
stripped
} else {
s
};
// Ensure that the string is not empty after stripping.
if s.is_empty() {
return Err(Error::new(InvalidBytesSize));
}
let caps = re.unwrap().captures(s).unwrap();
let num = caps.name("num");
let size: u128 = if let Some(num) = num {
let n = num.as_str().trim().parse::<u128>();
if n.is_err() {
return Err(Error::new(InvalidBytesSize));
}
n.unwrap()
} else {
// The last character should be the multiplier letter.
let last_char = s.chars().last().unwrap();
if !"kKmMgGtTpP".contains(last_char) {
return Err(Error::new(InvalidBytesSize));
};
}
let q = caps.name("mul");
let mul: u128 = if let Some(q) = q {
let t = m.get(q.as_str());
if let Some(t) = t {
*t
} else {
return Err(Error::new(InvalidBytesSize));
}
} else {
// The numeric part is everything before the multiplier letter.
let num_part = &s[..s.len() - last_char.len_utf8()];
if num_part.trim().is_empty() {
return Err(Error::new(InvalidBytesSize));
};
}
Ok(size * mul)
// Parse the numeric part into a u128.
let number: u128 = num_part
.trim()
.parse()
.map_err(|_| Error::new(InvalidBytesSize))?;
// Look up the multiplier in the provided HashMap.
let multiplier_key = last_char.to_string();
let multiplier = m
.get(&multiplier_key)
.ok_or_else(|| Error::new(InvalidBytesSize))?;
Ok(number * multiplier)
}
fn custom_size(mut size: f64, base: f64, m: &[String]) -> String {
@@ -305,3 +313,60 @@ fn custom_size(mut size: f64, base: f64, m: &[String]) -> String {
format!("{}{}", size, m[i].as_str())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_binary_size_valid() {
let m = get_binary_size_map();
// Valid inputs must include a multiplier letter.
assert_eq!(parse_size("1k", &m).unwrap(), KiB);
assert_eq!(parse_size("2m", &m).unwrap(), 2 * MiB);
assert_eq!(parse_size("3g", &m).unwrap(), 3 * GiB);
assert_eq!(parse_size("4t", &m).unwrap(), 4 * TiB);
assert_eq!(parse_size("5p", &m).unwrap(), 5 * PiB);
}
#[test]
fn test_decimal_size_valid() {
let m = get_decimal_size_map();
assert_eq!(parse_size("1k", &m).unwrap(), KB);
assert_eq!(parse_size("2m", &m).unwrap(), 2 * MB);
assert_eq!(parse_size("3g", &m).unwrap(), 3 * GB);
assert_eq!(parse_size("4t", &m).unwrap(), 4 * TB);
assert_eq!(parse_size("5p", &m).unwrap(), 5 * PB);
}
#[test]
fn test_trailing_b_suffix() {
let m = get_binary_size_map();
// Trailing 'b' or 'B' should be accepted.
assert_eq!(parse_size("1kb", &m).unwrap(), KiB);
assert_eq!(parse_size("2mB", &m).unwrap(), 2 * MiB);
}
#[test]
fn test_invalid_inputs() {
let m = get_binary_size_map();
// Missing multiplier letter results in error.
assert!(parse_size("1", &m).is_err());
// Invalid multiplier letter.
assert!(parse_size("10x", &m).is_err());
// Non-numeric input.
assert!(parse_size("abc", &m).is_err());
// Only multiplier letter with no number.
assert!(parse_size("k", &m).is_err());
// Number with an invalid trailing character.
assert!(parse_size("123z", &m).is_err());
}
#[test]
fn test_uppercase_multiplier_fails() {
let m = get_binary_size_map();
// Although the regex matches uppercase letters, the provided map only contains lowercase keys.
// Therefore, "1K" does not match any key and should produce an error.
assert!(parse_size("1K", &m).is_err());
}
}

View File

@@ -231,6 +231,7 @@ mod sealed {
})
}
#[allow(dead_code)]
fn get(&self, key: &str) -> Result<String> {
self.open_path(key, false).and_then(|mut file: File| {
let mut string = String::new();
@@ -255,6 +256,9 @@ pub trait Controller {
/// The file system path to the controller.
fn path(&self) -> &Path;
/// Root path of the file system to the controller.
fn base(&self) -> &Path;
/// Apply a set of resources to the Controller, invoking its internal functions to pass the
/// kernel the information.
fn apply(&self, res: &Resources) -> Result<()>;
@@ -307,6 +311,10 @@ where
self.get_path()
}
fn base(&self) -> &Path {
self.get_base()
}
/// Apply a set of resources to the Controller, invoking its internal functions to pass the
/// kernel the information.
fn apply(&self, res: &Resources) -> Result<()> {
@@ -777,7 +785,7 @@ impl From<u64> for CgroupPid {
}
}
impl<'a> From<&'a std::process::Child> for CgroupPid {
impl From<&std::process::Child> for CgroupPid {
fn from(u: &std::process::Child) -> CgroupPid {
CgroupPid { pid: u.id() as u64 }
}

View File

@@ -142,9 +142,7 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
let hier_unevict_line = ls.next().unwrap_or_default();
Ok(NumaStat {
total_pages: total_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
total_pages: total_line.split([' ', '=']).collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0),
total_pages_per_node: {
@@ -157,9 +155,7 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
})
.collect()
},
file_pages: file_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
file_pages: file_line.split([' ', '=']).collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0),
file_pages_per_node: {
@@ -172,9 +168,7 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
})
.collect()
},
anon_pages: anon_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
anon_pages: anon_line.split([' ', '=']).collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0),
anon_pages_per_node: {
@@ -187,9 +181,7 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
})
.collect()
},
unevictable_pages: unevict_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
unevictable_pages: unevict_line.split([' ', '=']).collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0),
unevictable_pages_per_node: {
@@ -204,9 +196,7 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
},
hierarchical_total_pages: {
if !hier_total_line.is_empty() {
hier_total_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
hier_total_line.split([' ', '=']).collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
} else {
@@ -229,9 +219,7 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
},
hierarchical_file_pages: {
if !hier_file_line.is_empty() {
hier_file_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
hier_file_line.split([' ', '=']).collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
} else {
@@ -254,9 +242,7 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
},
hierarchical_anon_pages: {
if !hier_anon_line.is_empty() {
hier_anon_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
hier_anon_line.split([' ', '=']).collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
} else {
@@ -279,9 +265,7 @@ fn parse_numa_stat(s: String) -> Result<NumaStat> {
},
hierarchical_unevictable_pages: {
if !hier_unevict_line.is_empty() {
hier_unevict_line
.split(|x| x == ' ' || x == '=')
.collect::<Vec<_>>()[1]
hier_unevict_line.split([' ', '=']).collect::<Vec<_>>()[1]
.parse::<u64>()
.unwrap_or(0)
} else {
@@ -540,10 +524,10 @@ impl ControllerInternal for MemController {
impl MemController {
/// Contructs a new `MemController` with `root` serving as the root of the control group.
pub fn new(root: PathBuf, v2: bool) -> Self {
pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self {
Self {
base: root.clone(),
path: root,
base: root,
path: point,
v2,
}
}
@@ -573,18 +557,33 @@ impl MemController {
// for v2
pub fn get_mem(&self) -> Result<SetMemory> {
let mut m: SetMemory = Default::default();
self.get_max_value("memory.high")
.map(|x| m.high = Some(x))?;
self.get_max_value("memory.low").map(|x| m.low = Some(x))?;
self.get_max_value("memory.max").map(|x| m.max = Some(x))?;
self.get_max_value("memory.min").map(|x| m.min = Some(x))?;
let m = SetMemory {
high: self
.get_max_value("memory.high")
.map_or(Some(MaxValue::default()), Some),
low: self
.get_max_value("memory.low")
.map_or(Some(MaxValue::Value(0)), Some),
max: self
.get_max_value("memory.max")
.map_or(Some(MaxValue::default()), Some),
min: self
.get_max_value("memory.min")
.map_or(Some(MaxValue::Value(0)), Some),
};
Ok(m)
}
fn memory_stat_v2(&self) -> Memory {
let set = self.get_mem().unwrap();
// NOTE: get_mem() always returns T, but let's
// still do `unwrap_or` for safety.
let set = self.get_mem().unwrap_or(SetMemory {
low: Some(MaxValue::Value(0)),
high: Some(MaxValue::default()),
max: Some(MaxValue::default()),
min: Some(MaxValue::Value(0)),
});
Memory {
fail_cnt: 0,
@@ -730,7 +729,7 @@ impl MemController {
.open_path("memory.swap.events", false)
.and_then(flat_keyed_to_hashmap)
.map(|x| *x.get("fail").unwrap_or(&0) as u64)
.unwrap(),
.unwrap_or(0),
limit_in_bytes: self
.open_path("memory.swap.max", false)
.and_then(read_i64_from)

View File

@@ -76,10 +76,10 @@ impl<'a> From<&'a Subsystem> for &'a NetClsController {
impl NetClsController {
/// Constructs a new `NetClsController` with `root` serving as the root of the control group.
pub fn new(root: PathBuf) -> Self {
pub fn new(point: PathBuf, root: PathBuf) -> Self {
Self {
base: root.clone(),
path: root,
base: root,
path: point,
}
}

View File

@@ -79,10 +79,10 @@ impl<'a> From<&'a Subsystem> for &'a NetPrioController {
impl NetPrioController {
/// Constructs a new `NetPrioController` with `root` serving as the root of the control group.
pub fn new(root: PathBuf) -> Self {
pub fn new(point: PathBuf, root: PathBuf) -> Self {
Self {
base: root.clone(),
path: root,
base: root,
path: point,
}
}
@@ -94,36 +94,26 @@ impl NetPrioController {
}
/// A map of priorities for each network interface.
#[allow(clippy::iter_nth_zero, clippy::unnecessary_unwrap)]
pub fn ifpriomap(&self) -> Result<HashMap<String, u64>> {
self.open_path("net_prio.ifpriomap", false)
.and_then(|file| {
let bf = BufReader::new(file);
bf.lines().fold(Ok(HashMap::new()), |acc, line| {
if acc.is_err() {
acc
} else {
let mut acc = acc.unwrap();
let l = line.unwrap();
let mut sp = l.split_whitespace();
bf.lines()
.map(|line| {
let line = line.map_err(|_| Error::new(ParseError))?;
let mut parts = line.split_whitespace();
let ifname = sp.nth(0);
let ifprio = sp.nth(1);
if ifname.is_none() || ifprio.is_none() {
Err(Error::new(ParseError))
} else {
let ifname = ifname.unwrap();
let ifprio = ifprio.unwrap().trim().parse();
match ifprio {
Err(e) => Err(Error::with_cause(ParseError, e)),
Ok(_) => {
acc.insert(ifname.to_string(), ifprio.unwrap());
Ok(acc)
}
}
}
}
})
let ifname = parts.next().ok_or(Error::new(ParseError))?;
let ifprio_str = parts.next().ok_or(Error::new(ParseError))?;
let ifprio = ifprio_str
.trim()
.parse()
.map_err(|e| Error::with_cause(ParseError, e))?;
Ok((ifname.to_string(), ifprio))
})
.collect::<Result<HashMap<String, _>>>()
})
}

View File

@@ -65,10 +65,10 @@ impl<'a> From<&'a Subsystem> for &'a PerfEventController {
impl PerfEventController {
/// Constructs a new `PerfEventController` with `root` serving as the root of the control group.
pub fn new(root: PathBuf) -> Self {
pub fn new(point: PathBuf, root: PathBuf) -> Self {
Self {
base: root.clone(),
path: root,
base: root,
path: point,
}
}
}

View File

@@ -92,10 +92,10 @@ impl<'a> From<&'a Subsystem> for &'a PidController {
impl PidController {
/// Constructors a new `PidController` instance, with `root` serving as the controller's root
/// directory.
pub fn new(root: PathBuf, v2: bool) -> Self {
pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self {
Self {
base: root.clone(),
path: root,
base: root,
path: point,
v2,
}
}

View File

@@ -68,10 +68,10 @@ impl<'a> From<&'a Subsystem> for &'a RdmaController {
impl RdmaController {
/// Constructs a new `RdmaController` with `root` serving as the root of the control group.
pub fn new(root: PathBuf) -> Self {
pub fn new(point: PathBuf, root: PathBuf) -> Self {
Self {
base: root.clone(),
path: root,
base: root,
path: point,
}
}
@@ -81,6 +81,11 @@ impl RdmaController {
.and_then(read_string_from)
}
/// Returns the max usage of RDMA/IB specific resources.
pub fn max(&self) -> Result<String> {
self.open_path("rdma.max", false).and_then(read_string_from)
}
/// Set a maximum usage for each RDMA/IB resource.
pub fn set_max(&self, max: &str) -> Result<()> {
self.open_path("rdma.max", true).and_then(|mut file| {

View File

@@ -62,10 +62,10 @@ impl<'a> From<&'a Subsystem> for &'a SystemdController {
impl SystemdController {
/// Constructs a new `SystemdController` with `root` serving as the root of the control group.
pub fn new(root: PathBuf, v2: bool) -> Self {
pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self {
Self {
base: root.clone(),
path: root,
base: root,
path: point,
_v2: v2,
}
}