├── .cargo └── config.toml ├── .clippy.toml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── enhancement-request.md │ └── feature_request.md └── workflows │ ├── PR-wip-checks.yaml │ ├── bvt.yaml │ └── commit-message-check.yaml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── LICENSE-Apache-2.0 ├── LICENSE-MIT ├── Makefile ├── README.md ├── src ├── fs │ ├── blkio.rs │ ├── cgroup.rs │ ├── cgroup_builder.rs │ ├── cpu.rs │ ├── cpuacct.rs │ ├── cpuset.rs │ ├── devices.rs │ ├── error.rs │ ├── events.rs │ ├── freezer.rs │ ├── hierarchies.rs │ ├── hugetlb.rs │ ├── memory.rs │ ├── mod.rs │ ├── net_cls.rs │ ├── net_prio.rs │ ├── perf_event.rs │ ├── pid.rs │ ├── rdma.rs │ └── systemd.rs ├── lib.rs ├── manager │ ├── conv.rs │ ├── error.rs │ ├── fs.rs │ ├── mod.rs │ └── systemd.rs ├── stats.rs └── systemd │ ├── consts.rs │ ├── cpu.rs │ ├── cpuset.rs │ ├── dbus │ ├── README.md │ ├── client.rs │ ├── error.rs │ ├── mod.rs │ ├── proxy.rs │ └── systemd_manager_proxy.rs │ ├── error.rs │ ├── memory.rs │ ├── mod.rs │ ├── pids.rs │ ├── props.rs │ └── utils.rs ├── tests ├── builder.rs ├── cgroup.rs ├── cpu.rs ├── cpuset.rs ├── devices.rs ├── hugetlb.rs ├── memory.rs ├── pids.rs └── resources.rs └── tools ├── create_cgroup.sh └── delete_cgroup.sh /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-unknown-linux-gnu] 2 | runner = 'sudo -E' 3 | -------------------------------------------------------------------------------- /.clippy.toml: -------------------------------------------------------------------------------- 1 | upper-case-acronyms-aggressive = true 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug, needs-review' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Expected behavior** 14 | A clear and concise description of what you expected to happen. 15 | 16 | **Additional context** 17 | Add any other context about the problem here. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement request 3 | about: Suggest an improvement to an existing feature 4 | title: '' 5 | labels: enhancement, needs-review 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Which feature do you think can be improved?** 11 | 12 | Specify the feature you think could be made better. 13 | 14 | **How can it be improved?** 15 | 16 | Describe how specifically you think it could be improved. 17 | 18 | **Additional Information** 19 | 20 | Anything else to add? 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'feature, needs-review' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/PR-wip-checks.yaml: -------------------------------------------------------------------------------- 1 | name: Pull request WIP checks 2 | on: 3 | pull_request: 4 | types: 5 | - opened 6 | - synchronize 7 | - reopened 8 | - edited 9 | - labeled 10 | - unlabeled 11 | 12 | jobs: 13 | pr_wip_check: 14 | runs-on: ubuntu-latest 15 | name: WIP Check 16 | steps: 17 | - name: WIP Check 18 | uses: tim-actions/wip-check@1c2a1ca6c110026b3e2297bb2ef39e1747b5a755 19 | with: 20 | labels: '["do-not-merge", "wip", "rfc"]' 21 | keywords: '["WIP", "wip", "RFC", "rfc", "dnm", "DNM", "do-not-merge"]' 22 | -------------------------------------------------------------------------------- /.github/workflows/bvt.yaml: -------------------------------------------------------------------------------- 1 | name: BVT 2 | on: [pull_request] 3 | env: 4 | RUST_VERSION: 1.85.1 5 | jobs: 6 | build: 7 | name: Build 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - run: rustup install ${{ env.RUST_VERSION }} && rustup default ${{ env.RUST_VERSION }} 12 | - run: make debug 13 | 14 | fmt: 15 | name: Format Check 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | - run: rustup install ${{ env.RUST_VERSION }} && rustup default ${{ env.RUST_VERSION }} 20 | - run: rustup component add rustfmt 21 | - run: make fmt 22 | clippy: 23 | name: Clippy Check 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v2 27 | - run: rustup install ${{ env.RUST_VERSION }} && rustup default ${{ env.RUST_VERSION }} 28 | - run: rustup component add clippy 29 | - run: make clippy 30 | test: 31 | name: Run Unit Test 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v2 35 | - run: rustup install ${{ env.RUST_VERSION }} && rustup default ${{ env.RUST_VERSION }} 36 | - run: make test 37 | 38 | 39 | -------------------------------------------------------------------------------- /.github/workflows/commit-message-check.yaml: -------------------------------------------------------------------------------- 1 | name: Commit Message Check 2 | on: 3 | pull_request: 4 | types: 5 | - opened 6 | - reopened 7 | - synchronize 8 | 9 | env: 10 | error_msg: |+ 11 | See the document below for help on formatting commits for the project. 12 | 13 | https://github.com/kata-containers/community/blob/master/CONTRIBUTING.md#patch-forma 14 | 15 | jobs: 16 | commit-message-check: 17 | runs-on: ubuntu-latest 18 | name: Commit Message Check 19 | steps: 20 | - name: Get PR Commits 21 | id: 'get-pr-commits' 22 | uses: tim-actions/get-pr-commits@v1.0.0 23 | with: 24 | token: ${{ secrets.GITHUB_TOKEN }} 25 | 26 | - name: DCO Check 27 | uses: tim-actions/dco@2fd0504dc0d27b33f542867c300c60840c6dcb20 28 | with: 29 | commits: ${{ steps.get-pr-commits.outputs.commits }} 30 | 31 | - name: Commit Body Missing Check 32 | if: ${{ success() || failure() }} 33 | uses: tim-actions/commit-body-check@v1.0.2 34 | with: 35 | commits: ${{ steps.get-pr-commits.outputs.commits }} 36 | 37 | - name: Check Subject Line Length 38 | if: ${{ success() || failure() }} 39 | uses: tim-actions/commit-message-checker-with-regex@v0.3.1 40 | with: 41 | commits: ${{ steps.get-pr-commits.outputs.commits }} 42 | pattern: '^.{0,75}(\n.*)*$' 43 | error: 'Subject too long (max 75)' 44 | post_error: ${{ env.error_msg }} 45 | 46 | - name: Check Body Line Length 47 | if: ${{ success() || failure() }} 48 | uses: tim-actions/commit-message-checker-with-regex@v0.3.1 49 | with: 50 | commits: ${{ steps.get-pr-commits.outputs.commits }} 51 | pattern: '^.+(\n([a-zA-Z].{0,149}|[^a-zA-Z\n].*|Signed-off-by:.*|))+$' 52 | error: 'Body line too long (max 72)' 53 | post_error: ${{ env.error_msg }} 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cgroups-rs" 3 | description = "Native Rust crate for managing control groups on Linux" 4 | repository = "https://github.com/kata-containers/cgroups-rs" 5 | keywords = ["linux", "cgroup", "containers", "isolation"] 6 | categories = ["os", "api-bindings", "os::unix-apis"] 7 | license = "MIT OR Apache-2.0" 8 | version = "0.4.0" 9 | authors = ["The Kata Containers community ", "Levente Kurusa ", "Sam Wilson "] 10 | edition = "2018" 11 | homepage = "https://github.com/kata-containers/cgroups-rs" 12 | readme = "README.md" 13 | 14 | [dependencies] 15 | log = "0.4" 16 | nix = { version = "0.25.0", default-features = false, features = ["event", "fs", "process"] } 17 | libc = "0.2" 18 | serde = { version = "1.0", features = ["derive"], optional = true } 19 | thiserror = "1" 20 | oci-spec = { version = "0.8.1", optional = true } 21 | zbus = "5.8" 22 | bit-vec = "0.6" 23 | 24 | [dev-dependencies] 25 | libc = "0.2.76" 26 | rand = "0.8" 27 | nix = "0.25" 28 | 29 | [features] 30 | default = [] 31 | oci = ["oci-spec"] 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This crate is licensed under either of 2 | 3 | - "Apache License, Version 2.0, (See LICENSE-Apache-2.0 file); or 4 | - "MIT license" (See LICENSE-MIT file), 5 | 6 | at your option. 7 | -------------------------------------------------------------------------------- /LICENSE-Apache-2.0: -------------------------------------------------------------------------------- 1 | Copyright 2018 Levente Kurusa 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Levente Kurusa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: debug fmt test 2 | 3 | # 4 | # Build 5 | # 6 | 7 | .PHONY: debug 8 | debug: 9 | RUSTFLAGS="--deny warnings" cargo build 10 | 11 | .PHONY: release 12 | release: 13 | cargo build --release 14 | 15 | .PHONY: build 16 | build: debug 17 | 18 | # 19 | # Tests and linters 20 | # 21 | 22 | # Tests that manipulate cgroups should run in sequence, so that 23 | # `--test-threads=1` is used. 24 | test: test-systemd test-fs-manager test-systemd-manager 25 | cargo test --all-features -- --color always \ 26 | --nocapture \ 27 | --skip systemd::dbus::client::tests \ 28 | --skip manager::fs::tests \ 29 | --skip manager::systemd::tests 30 | 31 | .PHONY: test-systemd 32 | # Tests that manipulate cgroups should run in sequence, so that 33 | # `--test-threads=1` is used. 34 | test-systemd: 35 | cargo test --package cgroups-rs --lib \ 36 | -- systemd::dbus::client::tests \ 37 | --color always --nocapture \ 38 | --test-threads=1 39 | 40 | .PHONY: test-fs-manager 41 | # See test-systemd 42 | test-fs-manager: 43 | cargo test --all-features --package cgroups-rs \ 44 | --lib -- manager::fs::tests \ 45 | --color always --nocapture --test-threads=1 46 | 47 | .PHONY: test-systemd-manager 48 | # See test-systemd 49 | test-systemd-manager: 50 | cargo test --all-features --package cgroups-rs \ 51 | --lib -- manager::systemd::tests \ 52 | --color always --nocapture --test-threads=1 53 | 54 | .PHONY: check 55 | check: fmt clippy 56 | 57 | 58 | .PHONY: fmt 59 | fmt: 60 | cargo fmt --all -- --check 61 | 62 | .PHONY: clippy 63 | clippy: 64 | cargo clippy --all-targets --all-features -- -D warnings 65 | 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cgroups-rs ![Build](https://travis-ci.org/kata-containers/cgroups-rs.svg?branch=master) 2 | Native Rust library for managing control groups under Linux 3 | 4 | Both v1 and v2 of cgroups are supported. 5 | 6 | # Examples 7 | 8 | ## Create a control group using the builder pattern 9 | 10 | ``` rust 11 | 12 | 13 | use cgroups_rs::*; 14 | use cgroups_rs::cgroup_builder::*; 15 | 16 | // Acquire a handle for the cgroup hierarchy. 17 | let hier = cgroups_rs::hierarchies::auto(); 18 | 19 | // Use the builder pattern (see the documentation to create the control group) 20 | // 21 | // This creates a control group named "example" in the V1 hierarchy. 22 | let cg: Cgroup = CgroupBuilder::new("example") 23 | .cpu() 24 | .shares(85) 25 | .done() 26 | .build(hier); 27 | 28 | // Now `cg` is a control group that gets 85% of the CPU time in relative to 29 | // other control groups. 30 | 31 | // Get a handle to the CPU controller. 32 | let cpus: &cgroups_rs::cpu::CpuController = cg.controller_of().unwrap(); 33 | cpus.add_task(&CgroupPid::from(1234u64)); 34 | 35 | // [...] 36 | 37 | // Finally, clean up and delete the control group. 38 | cg.delete(); 39 | 40 | // Note that `Cgroup` does not implement `Drop` and therefore when the 41 | // structure is dropped, the Cgroup will stay around. This is because, later 42 | // you can then re-create the `Cgroup` using `load()`. We aren't too set on 43 | // this behavior, so it might change in the feature. Rest assured, it will be a 44 | // major version change. 45 | ``` 46 | 47 | # Disclaimer 48 | 49 | This crate is licensed under: 50 | 51 | - MIT License (see LICENSE-MIT); or 52 | - Apache 2.0 License (see LICENSE-Apache-2.0), 53 | 54 | at your option. 55 | 56 | Please note that this crate is under heavy development, we will use sematic 57 | versioning, but during the `0.0.*` phase, no guarantees are made about 58 | backwards compatibility. 59 | 60 | Regardless, check back often and thanks for taking a look! 61 | -------------------------------------------------------------------------------- /src/fs/cgroup_builder.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 Ant Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! This module allows the user to create a control group using the Builder pattern. 8 | //! # Example 9 | //! 10 | //! The following example demonstrates how the control group builder looks like. The user 11 | //! specifies the name of the control group (here: "hello") and the hierarchy it belongs to (here: 12 | //! a V1 hierarchy). Next, the user selects a subsystem by calling functions like `memory()`, 13 | //! `cpu()` and `devices()`. The user can then add restrictions and details via subsystem-specific 14 | //! calls. To finalize a subsystem, the user may call `done()`. Finally, if the control group build 15 | //! is done and all requirements/restrictions have been specified, the control group can be created 16 | //! by a call to `build()`. 17 | //! 18 | //! ```rust,no_run 19 | //! # use cgroups_rs::fs::*; 20 | //! # use cgroups_rs::fs::devices::*; 21 | //! # use cgroups_rs::fs::cgroup_builder::*; 22 | //! let h = cgroups_rs::fs::hierarchies::auto(); 23 | //! let cgroup: Cgroup = CgroupBuilder::new("hello") 24 | //! .memory() 25 | //! .kernel_memory_limit(1024 * 1024) 26 | //! .memory_hard_limit(1024 * 1024) 27 | //! .done() 28 | //! .cpu() 29 | //! .shares(100) 30 | //! .done() 31 | //! .devices() 32 | //! .device(1000, 10, DeviceType::Block, true, 33 | //! vec![DevicePermissions::Read, 34 | //! DevicePermissions::Write, 35 | //! DevicePermissions::MkNod]) 36 | //! .device(6, 1, DeviceType::Char, false, vec![]) 37 | //! .done() 38 | //! .network() 39 | //! .class_id(1337) 40 | //! .priority("eth0".to_string(), 100) 41 | //! .priority("wl0".to_string(), 200) 42 | //! .done() 43 | //! .hugepages() 44 | //! .limit("2M".to_string(), 0) 45 | //! .limit("4M".to_string(), 4 * 1024 * 1024 * 100) 46 | //! .limit("2G".to_string(), 2 * 1024 * 1024 * 1024) 47 | //! .done() 48 | //! .blkio() 49 | //! .weight(123) 50 | //! .leaf_weight(99) 51 | //! .weight_device(6, 1, Some(100), Some(55)) 52 | //! .weight_device(6, 1, Some(100), Some(55)) 53 | //! .throttle_iops() 54 | //! .read(6, 1, 10) 55 | //! .write(11, 1, 100) 56 | //! .throttle_bps() 57 | //! .read(6, 1, 10) 58 | //! .write(11, 1, 100) 59 | //! .done() 60 | //! .build(h).unwrap(); 61 | //! ``` 62 | 63 | use crate::fs::{ 64 | BlkIoDeviceResource, BlkIoDeviceThrottleResource, Cgroup, DeviceResource, Error, Hierarchy, 65 | HugePageResource, MaxValue, NetworkPriority, Resources, 66 | }; 67 | 68 | macro_rules! gen_setter { 69 | ($res:ident, $cont:ident, $func:ident, $name:ident, $ty:ty) => { 70 | /// See the similarly named function in the respective controller. 71 | pub fn $name(mut self, $name: $ty) -> Self { 72 | self.cgroup.resources.$res.$name = Some($name); 73 | self 74 | } 75 | }; 76 | } 77 | 78 | /// A control group builder instance 79 | pub struct CgroupBuilder { 80 | name: String, 81 | /// Internal, unsupported field: use the associated builders instead. 82 | resources: Resources, 83 | /// List of controllers specifically enabled in the control group. 84 | specified_controllers: Option>, 85 | } 86 | 87 | impl CgroupBuilder { 88 | /// Start building a control group with the supplied hierarchy and name pair. 89 | /// 90 | /// Note that this does not actually create the control group until `build()` is called. 91 | pub fn new(name: &str) -> CgroupBuilder { 92 | CgroupBuilder { 93 | name: name.to_owned(), 94 | resources: Resources::default(), 95 | specified_controllers: None, 96 | } 97 | } 98 | 99 | /// Builds the memory resources of the control group. 100 | pub fn memory(self) -> MemoryResourceBuilder { 101 | MemoryResourceBuilder { cgroup: self } 102 | } 103 | 104 | /// Builds the pid resources of the control group. 105 | pub fn pid(self) -> PidResourceBuilder { 106 | PidResourceBuilder { cgroup: self } 107 | } 108 | 109 | /// Builds the cpu resources of the control group. 110 | pub fn cpu(self) -> CpuResourceBuilder { 111 | CpuResourceBuilder { cgroup: self } 112 | } 113 | 114 | /// Builds the devices resources of the control group, disallowing or 115 | /// allowing access to certain devices in the system. 116 | pub fn devices(self) -> DeviceResourceBuilder { 117 | DeviceResourceBuilder { cgroup: self } 118 | } 119 | 120 | /// Builds the network resources of the control group, setting class id, or 121 | /// various priorities on networking interfaces. 122 | pub fn network(self) -> NetworkResourceBuilder { 123 | NetworkResourceBuilder { cgroup: self } 124 | } 125 | 126 | /// Builds the hugepage/hugetlb resources available to the control group. 127 | pub fn hugepages(self) -> HugepagesResourceBuilder { 128 | HugepagesResourceBuilder { cgroup: self } 129 | } 130 | 131 | /// Builds the block I/O resources available for the control group. 132 | pub fn blkio(self) -> BlkIoResourcesBuilder { 133 | BlkIoResourcesBuilder { 134 | cgroup: self, 135 | throttling_iops: false, 136 | } 137 | } 138 | 139 | /// Finalize the control group, consuming the builder and creating the control group. 140 | pub fn build(self, hier: Box) -> Result { 141 | if let Some(controllers) = self.specified_controllers { 142 | let cg = Cgroup::new_with_specified_controllers(hier, self.name, Some(controllers))?; 143 | cg.apply(&self.resources)?; 144 | Ok(cg) 145 | } else { 146 | let cg = Cgroup::new(hier, self.name)?; 147 | cg.apply(&self.resources)?; 148 | Ok(cg) 149 | } 150 | } 151 | 152 | /// Specifically enable some controllers in the control group. 153 | pub fn set_specified_controllers(mut self, specified_controllers: Vec) -> Self { 154 | self.specified_controllers = Some(specified_controllers); 155 | self 156 | } 157 | } 158 | 159 | /// A builder that configures the memory controller of a control group. 160 | pub struct MemoryResourceBuilder { 161 | cgroup: CgroupBuilder, 162 | } 163 | 164 | impl MemoryResourceBuilder { 165 | gen_setter!( 166 | memory, 167 | MemController, 168 | set_kmem_limit, 169 | kernel_memory_limit, 170 | i64 171 | ); 172 | gen_setter!(memory, MemController, set_limit, memory_hard_limit, i64); 173 | gen_setter!( 174 | memory, 175 | MemController, 176 | set_soft_limit, 177 | memory_soft_limit, 178 | i64 179 | ); 180 | gen_setter!( 181 | memory, 182 | MemController, 183 | set_tcp_limit, 184 | kernel_tcp_memory_limit, 185 | i64 186 | ); 187 | gen_setter!( 188 | memory, 189 | MemController, 190 | set_memswap_limit, 191 | memory_swap_limit, 192 | i64 193 | ); 194 | gen_setter!(memory, MemController, set_swappiness, swappiness, u64); 195 | 196 | /// Finish the construction of the memory resources of a control group. 197 | pub fn done(self) -> CgroupBuilder { 198 | self.cgroup 199 | } 200 | } 201 | 202 | /// A builder that configures the pid controller of a control group. 203 | pub struct PidResourceBuilder { 204 | cgroup: CgroupBuilder, 205 | } 206 | 207 | impl PidResourceBuilder { 208 | gen_setter!( 209 | pid, 210 | PidController, 211 | set_pid_max, 212 | maximum_number_of_processes, 213 | MaxValue 214 | ); 215 | 216 | /// Finish the construction of the pid resources of a control group. 217 | pub fn done(self) -> CgroupBuilder { 218 | self.cgroup 219 | } 220 | } 221 | 222 | /// A builder that configures the cpuset & cpu controllers of a control group. 223 | pub struct CpuResourceBuilder { 224 | cgroup: CgroupBuilder, 225 | } 226 | 227 | impl CpuResourceBuilder { 228 | gen_setter!(cpu, CpuSetController, set_cpus, cpus, String); 229 | gen_setter!(cpu, CpuSetController, set_mems, mems, String); 230 | gen_setter!(cpu, CpuController, set_shares, shares, u64); 231 | gen_setter!(cpu, CpuController, set_cfs_quota, quota, i64); 232 | gen_setter!(cpu, CpuController, set_cfs_period, period, u64); 233 | gen_setter!(cpu, CpuController, set_rt_runtime, realtime_runtime, i64); 234 | gen_setter!(cpu, CpuController, set_rt_period, realtime_period, u64); 235 | 236 | /// Finish the construction of the cpu resources of a control group. 237 | pub fn done(self) -> CgroupBuilder { 238 | self.cgroup 239 | } 240 | } 241 | 242 | /// A builder that configures the devices controller of a control group. 243 | pub struct DeviceResourceBuilder { 244 | cgroup: CgroupBuilder, 245 | } 246 | 247 | impl DeviceResourceBuilder { 248 | /// Restrict (or allow) a device to the tasks inside the control group. 249 | pub fn device( 250 | mut self, 251 | major: i64, 252 | minor: i64, 253 | devtype: crate::fs::devices::DeviceType, 254 | allow: bool, 255 | access: Vec, 256 | ) -> DeviceResourceBuilder { 257 | self.cgroup.resources.devices.devices.push(DeviceResource { 258 | allow, 259 | devtype, 260 | major, 261 | minor, 262 | access, 263 | }); 264 | self 265 | } 266 | 267 | /// Finish the construction of the devices resources of a control group. 268 | pub fn done(self) -> CgroupBuilder { 269 | self.cgroup 270 | } 271 | } 272 | 273 | /// A builder that configures the net_cls & net_prio controllers of a control group. 274 | pub struct NetworkResourceBuilder { 275 | cgroup: CgroupBuilder, 276 | } 277 | 278 | impl NetworkResourceBuilder { 279 | gen_setter!(network, NetclsController, set_class, class_id, u64); 280 | 281 | /// Set the priority of the tasks when operating on a networking device defined by `name` to be 282 | /// `priority`. 283 | pub fn priority(mut self, name: String, priority: u64) -> NetworkResourceBuilder { 284 | self.cgroup 285 | .resources 286 | .network 287 | .priorities 288 | .push(NetworkPriority { name, priority }); 289 | self 290 | } 291 | 292 | /// Finish the construction of the network resources of a control group. 293 | pub fn done(self) -> CgroupBuilder { 294 | self.cgroup 295 | } 296 | } 297 | 298 | /// A builder that configures the hugepages controller of a control group. 299 | pub struct HugepagesResourceBuilder { 300 | cgroup: CgroupBuilder, 301 | } 302 | 303 | impl HugepagesResourceBuilder { 304 | /// Limit the usage of certain hugepages (determined by `size`) to be at most `limit` bytes. 305 | pub fn limit(mut self, size: String, limit: u64) -> HugepagesResourceBuilder { 306 | self.cgroup 307 | .resources 308 | .hugepages 309 | .limits 310 | .push(HugePageResource { size, limit }); 311 | self 312 | } 313 | 314 | /// Finish the construction of the network resources of a control group. 315 | pub fn done(self) -> CgroupBuilder { 316 | self.cgroup 317 | } 318 | } 319 | 320 | /// A builder that configures the blkio controller of a control group. 321 | pub struct BlkIoResourcesBuilder { 322 | cgroup: CgroupBuilder, 323 | throttling_iops: bool, 324 | } 325 | 326 | impl BlkIoResourcesBuilder { 327 | gen_setter!(blkio, BlkIoController, set_weight, weight, u16); 328 | gen_setter!(blkio, BlkIoController, set_leaf_weight, leaf_weight, u16); 329 | 330 | /// Set the weight of a certain device. 331 | pub fn weight_device( 332 | mut self, 333 | major: u64, 334 | minor: u64, 335 | weight: Option, 336 | leaf_weight: Option, 337 | ) -> BlkIoResourcesBuilder { 338 | self.cgroup 339 | .resources 340 | .blkio 341 | .weight_device 342 | .push(BlkIoDeviceResource { 343 | major, 344 | minor, 345 | weight, 346 | leaf_weight, 347 | }); 348 | self 349 | } 350 | 351 | /// Start configuring the I/O operations per second metric. 352 | pub fn throttle_iops(mut self) -> BlkIoResourcesBuilder { 353 | self.throttling_iops = true; 354 | self 355 | } 356 | 357 | /// Start configuring the bytes per second metric. 358 | pub fn throttle_bps(mut self) -> BlkIoResourcesBuilder { 359 | self.throttling_iops = false; 360 | self 361 | } 362 | 363 | /// Limit the read rate of the current metric for a certain device. 364 | pub fn read(mut self, major: u64, minor: u64, rate: u64) -> BlkIoResourcesBuilder { 365 | let throttle = BlkIoDeviceThrottleResource { major, minor, rate }; 366 | if self.throttling_iops { 367 | self.cgroup 368 | .resources 369 | .blkio 370 | .throttle_read_iops_device 371 | .push(throttle); 372 | } else { 373 | self.cgroup 374 | .resources 375 | .blkio 376 | .throttle_read_bps_device 377 | .push(throttle); 378 | } 379 | self 380 | } 381 | 382 | /// Limit the write rate of the current metric for a certain device. 383 | pub fn write(mut self, major: u64, minor: u64, rate: u64) -> BlkIoResourcesBuilder { 384 | let throttle = BlkIoDeviceThrottleResource { major, minor, rate }; 385 | if self.throttling_iops { 386 | self.cgroup 387 | .resources 388 | .blkio 389 | .throttle_write_iops_device 390 | .push(throttle); 391 | } else { 392 | self.cgroup 393 | .resources 394 | .blkio 395 | .throttle_write_bps_device 396 | .push(throttle); 397 | } 398 | self 399 | } 400 | 401 | /// Finish the construction of the blkio resources of a control group. 402 | pub fn done(self) -> CgroupBuilder { 403 | self.cgroup 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /src/fs/cpu.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 Ant Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! This module contains the implementation of the `cpu` cgroup subsystem. 8 | //! 9 | //! See the Kernel's documentation for more information about this subsystem, found at: 10 | //! [Documentation/scheduler/sched-design-CFS.txt](https://www.kernel.org/doc/Documentation/scheduler/sched-design-CFS.txt) 11 | //! paragraph 7 ("GROUP SCHEDULER EXTENSIONS TO CFS"). 12 | use std::fs::File; 13 | use std::io::{Read, Write}; 14 | use std::path::PathBuf; 15 | 16 | use crate::fs::error::ErrorKind::*; 17 | use crate::fs::error::*; 18 | use crate::fs::{parse_max_value, read_i64_from, read_u64_from}; 19 | 20 | use crate::fs::{ 21 | ControllIdentifier, ControllerInternal, Controllers, CpuResources, CustomizedAttribute, 22 | MaxValue, Resources, Subsystem, 23 | }; 24 | 25 | /// A controller that allows controlling the `cpu` subsystem of a Cgroup. 26 | /// 27 | /// In essence, it allows gathering information about how much the tasks inside the control group 28 | /// are using the CPU and creating rules that limit their usage. Note that this crate does not yet 29 | /// support managing realtime tasks. 30 | #[derive(Debug, Clone)] 31 | pub struct CpuController { 32 | base: PathBuf, 33 | path: PathBuf, 34 | v2: bool, 35 | } 36 | 37 | /// The current state of the control group and its processes. 38 | #[derive(Debug)] 39 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 40 | pub struct Cpu { 41 | /// Reports CPU time statistics. 42 | /// 43 | /// Corresponds the `cpu.stat` file in `cpu` control group. 44 | pub stat: String, 45 | } 46 | 47 | /// The current state of the control group and its processes. 48 | #[derive(Debug)] 49 | struct CfsQuotaAndPeriod { 50 | quota: MaxValue, 51 | period: u64, 52 | } 53 | 54 | impl ControllerInternal for CpuController { 55 | fn control_type(&self) -> Controllers { 56 | Controllers::Cpu 57 | } 58 | 59 | fn get_path(&self) -> &PathBuf { 60 | &self.path 61 | } 62 | fn get_path_mut(&mut self) -> &mut PathBuf { 63 | &mut self.path 64 | } 65 | 66 | fn get_base(&self) -> &PathBuf { 67 | &self.base 68 | } 69 | 70 | fn is_v2(&self) -> bool { 71 | self.v2 72 | } 73 | 74 | fn apply(&self, res: &Resources) -> Result<()> { 75 | // get the resources that apply to this controller 76 | let res: &CpuResources = &res.cpu; 77 | 78 | update_and_test!(self, set_shares, res.shares, shares); 79 | update_and_test!(self, set_cfs_period, res.period, cfs_period); 80 | update_and_test!(self, set_cfs_quota, res.quota, cfs_quota); 81 | 82 | res.attrs.iter().for_each(|(k, v)| { 83 | let _ = self.set(k, v); 84 | }); 85 | 86 | // TODO: rt properties (CONFIG_RT_GROUP_SCHED) are not yet supported 87 | 88 | Ok(()) 89 | } 90 | } 91 | 92 | impl ControllIdentifier for CpuController { 93 | fn controller_type() -> Controllers { 94 | Controllers::Cpu 95 | } 96 | } 97 | 98 | impl<'a> From<&'a Subsystem> for &'a CpuController { 99 | fn from(sub: &'a Subsystem) -> &'a CpuController { 100 | unsafe { 101 | match sub { 102 | Subsystem::Cpu(c) => c, 103 | _ => { 104 | assert_eq!(1, 0); 105 | let v = std::mem::MaybeUninit::uninit(); 106 | v.assume_init() 107 | } 108 | } 109 | } 110 | } 111 | } 112 | 113 | impl CpuController { 114 | /// Contructs a new `CpuController` with `root` serving as the root of the control group. 115 | pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self { 116 | Self { 117 | base: root, 118 | path: point, 119 | v2, 120 | } 121 | } 122 | 123 | /// Returns CPU time statistics based on the processes in the control group. 124 | pub fn cpu(&self) -> Cpu { 125 | Cpu { 126 | stat: self 127 | .open_path("cpu.stat", false) 128 | .and_then(|mut file| { 129 | let mut s = String::new(); 130 | let res = file.read_to_string(&mut s); 131 | match res { 132 | Ok(_) => Ok(s), 133 | Err(e) => Err(Error::with_cause(ReadFailed("cpu.stat".to_string()), e)), 134 | } 135 | }) 136 | .unwrap_or_default(), 137 | } 138 | } 139 | 140 | /// Configures the CPU bandwidth (in relative relation to other control groups and this control 141 | /// group's parent). 142 | /// 143 | /// For example, setting control group `A`'s `shares` to `100`, and control group `B`'s 144 | /// `shares` to `200` ensures that control group `B` receives twice as much as CPU bandwidth. 145 | /// (Assuming both `A` and `B` are of the same parent) 146 | pub fn set_shares(&self, shares: u64) -> Result<()> { 147 | let mut file_name = "cpu.shares"; 148 | if self.v2 { 149 | file_name = "cpu.weight"; 150 | } 151 | // NOTE: .CpuShares is not used here. Conversion is the caller's responsibility. 152 | self.open_path(file_name, true).and_then(|mut file| { 153 | file.write_all(shares.to_string().as_ref()).map_err(|e| { 154 | Error::with_cause(WriteFailed(file_name.to_string(), shares.to_string()), e) 155 | }) 156 | }) 157 | } 158 | 159 | /// Retrieve the CPU bandwidth that this control group (relative to other control groups and 160 | /// this control group's parent) can use. 161 | pub fn shares(&self) -> Result { 162 | let mut file = "cpu.shares"; 163 | if self.v2 { 164 | file = "cpu.weight"; 165 | } 166 | self.open_path(file, false).and_then(read_u64_from) 167 | } 168 | 169 | /// Specify a period (when using the CFS scheduler) of time in microseconds for how often this 170 | /// control group's access to the CPU should be reallocated. 171 | pub fn set_cfs_period(&self, us: u64) -> Result<()> { 172 | if self.v2 { 173 | return self.set_cfs_quota_and_period(None, Some(us)); 174 | } 175 | self.open_path("cpu.cfs_period_us", true) 176 | .and_then(|mut file| { 177 | file.write_all(us.to_string().as_ref()).map_err(|e| { 178 | Error::with_cause( 179 | WriteFailed("cpu.cfs_period_us".to_string(), us.to_string()), 180 | e, 181 | ) 182 | }) 183 | }) 184 | } 185 | 186 | /// Retrieve the period of time of how often this cgroup's access to the CPU should be 187 | /// reallocated in microseconds. 188 | pub fn cfs_period(&self) -> Result { 189 | if self.v2 { 190 | let current_value = self 191 | .open_path("cpu.max", false) 192 | .and_then(parse_cfs_quota_and_period)?; 193 | return Ok(current_value.period); 194 | } 195 | self.open_path("cpu.cfs_period_us", false) 196 | .and_then(read_u64_from) 197 | } 198 | 199 | /// Specify a quota (when using the CFS scheduler) of time in microseconds for which all tasks 200 | /// in this control group can run during one period (see: `set_cfs_period()`). 201 | pub fn set_cfs_quota(&self, us: i64) -> Result<()> { 202 | if self.v2 { 203 | return self.set_cfs_quota_and_period(Some(us), None); 204 | } 205 | self.open_path("cpu.cfs_quota_us", true) 206 | .and_then(|mut file| { 207 | file.write_all(us.to_string().as_ref()).map_err(|e| { 208 | Error::with_cause( 209 | WriteFailed("cpu.cfs_quota_us".to_string(), us.to_string()), 210 | e, 211 | ) 212 | }) 213 | }) 214 | } 215 | 216 | /// Retrieve the quota of time for which all tasks in this cgroup can run during one period, in 217 | /// microseconds. 218 | pub fn cfs_quota(&self) -> Result { 219 | if self.v2 { 220 | let current_value = self 221 | .open_path("cpu.max", false) 222 | .and_then(parse_cfs_quota_and_period)?; 223 | return Ok(current_value.quota.to_i64()); 224 | } 225 | 226 | self.open_path("cpu.cfs_quota_us", false) 227 | .and_then(read_i64_from) 228 | } 229 | 230 | pub fn set_cfs_quota_and_period(&self, quota: Option, period: Option) -> Result<()> { 231 | if !self.v2 { 232 | if let Some(q) = quota { 233 | self.set_cfs_quota(q)?; 234 | } 235 | if let Some(p) = period { 236 | self.set_cfs_period(p)?; 237 | } 238 | return Ok(()); 239 | } 240 | 241 | // https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html 242 | 243 | // cpu.max 244 | // A read-write two value file which exists on non-root cgroups. The default is “max 100000”. 245 | // The maximum bandwidth limit. It’s in the following format: 246 | // $MAX $PERIOD 247 | // which indicates that the group may consume upto $MAX in each $PERIOD duration. 248 | // “max” for $MAX indicates no limit. If only one number is written, $MAX is updated. 249 | 250 | let current_value = self 251 | .open_path("cpu.max", false) 252 | .and_then(parse_cfs_quota_and_period)?; 253 | 254 | let new_quota = if let Some(q) = quota { 255 | if q > 0 { 256 | q.to_string() 257 | } else { 258 | "max".to_string() 259 | } 260 | } else { 261 | current_value.quota.to_string() 262 | }; 263 | 264 | let new_period = if let Some(p) = period { 265 | p.to_string() 266 | } else { 267 | current_value.period.to_string() 268 | }; 269 | 270 | let line = format!("{} {}", new_quota, new_period); 271 | self.open_path("cpu.max", true).and_then(|mut file| { 272 | file.write_all(line.as_ref()) 273 | .map_err(|e| Error::with_cause(WriteFailed("cpu.max".to_string(), line), e)) 274 | }) 275 | } 276 | 277 | pub fn set_rt_runtime(&self, us: i64) -> Result<()> { 278 | self.open_path("cpu.rt_runtime_us", true) 279 | .and_then(|mut file| { 280 | file.write_all(us.to_string().as_ref()).map_err(|e| { 281 | Error::with_cause( 282 | WriteFailed("cpu.rt_runtime_us".to_string(), us.to_string()), 283 | e, 284 | ) 285 | }) 286 | }) 287 | } 288 | 289 | pub fn set_rt_period_us(&self, us: u64) -> Result<()> { 290 | self.open_path("cpu.rt_period_us", true) 291 | .and_then(|mut file| { 292 | file.write_all(us.to_string().as_ref()).map_err(|e| { 293 | Error::with_cause( 294 | WriteFailed("cpu.rt_period_us".to_string(), us.to_string()), 295 | e, 296 | ) 297 | }) 298 | }) 299 | } 300 | } 301 | 302 | impl CustomizedAttribute for CpuController {} 303 | 304 | fn parse_cfs_quota_and_period(mut file: File) -> Result { 305 | let mut content = String::new(); 306 | file.read_to_string(&mut content) 307 | .map_err(|e| Error::with_cause(ReadFailed("cpu.max".to_string()), e))?; 308 | 309 | let fields = content.trim().split(' ').collect::>(); 310 | if fields.len() != 2 { 311 | return Err(Error::from_string(format!("invaild format: {}", content))); 312 | } 313 | 314 | let quota = parse_max_value(fields[0])?; 315 | let period = fields[1] 316 | .parse::() 317 | .map_err(|e| Error::with_cause(ParseError, e))?; 318 | 319 | Ok(CfsQuotaAndPeriod { quota, period }) 320 | } 321 | -------------------------------------------------------------------------------- /src/fs/cpuacct.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | //! This module contains the implementation of the `cpuacct` cgroup subsystem. 7 | //! 8 | //! See the Kernel's documentation for more information about this subsystem, found at: 9 | //! [Documentation/cgroup-v1/cpuacct.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt) 10 | use std::io::Write; 11 | use std::path::PathBuf; 12 | 13 | use crate::fs::error::ErrorKind::*; 14 | use crate::fs::error::*; 15 | 16 | use crate::fs::{read_string_from, read_u64_from}; 17 | use crate::fs::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; 18 | 19 | /// A controller that allows controlling the `cpuacct` subsystem of a Cgroup. 20 | /// 21 | /// In essence, this control group provides accounting (hence the name `cpuacct`) for CPU usage of 22 | /// the tasks in the control group. 23 | #[derive(Debug, Clone)] 24 | pub struct CpuAcctController { 25 | base: PathBuf, 26 | path: PathBuf, 27 | } 28 | 29 | /// Represents the statistics retrieved from the control group. 30 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 31 | pub struct CpuAcct { 32 | /// Divides the time used by the tasks into `user` time and `system` time. 33 | pub stat: String, 34 | /// Total CPU time (in nanoseconds) spent by the tasks. 35 | pub usage: u64, 36 | /// Total CPU time (in nanoseconds) spent by the tasks, broken down by CPU and by whether the 37 | /// time spent is `user` time or `system` time. 38 | /// 39 | /// An example is as follows: 40 | /// ```text 41 | /// cpu user system 42 | /// 0 8348363768 0 43 | /// 1 8324369100 0 44 | /// 2 8598185449 0 45 | /// 3 8648262473 0 46 | /// ``` 47 | pub usage_all: String, 48 | /// CPU time (in nanoseconds) spent by the tasks, broken down by each CPU. 49 | /// Times spent in each CPU are separated by a space. 50 | pub usage_percpu: String, 51 | /// As for `usage_percpu`, but the `system` time spent. 52 | pub usage_percpu_sys: String, 53 | /// As for `usage_percpu`, but the `user` time spent. 54 | pub usage_percpu_user: String, 55 | /// CPU time (in nanoseconds) spent by the tasks that counted for `system` time. 56 | pub usage_sys: u64, 57 | /// CPU time (in nanoseconds) spent by the tasks that counted for `user` time. 58 | pub usage_user: u64, 59 | } 60 | 61 | impl ControllerInternal for CpuAcctController { 62 | fn control_type(&self) -> Controllers { 63 | Controllers::CpuAcct 64 | } 65 | fn get_path(&self) -> &PathBuf { 66 | &self.path 67 | } 68 | fn get_path_mut(&mut self) -> &mut PathBuf { 69 | &mut self.path 70 | } 71 | fn get_base(&self) -> &PathBuf { 72 | &self.base 73 | } 74 | 75 | fn apply(&self, _res: &Resources) -> Result<()> { 76 | Ok(()) 77 | } 78 | } 79 | 80 | impl ControllIdentifier for CpuAcctController { 81 | fn controller_type() -> Controllers { 82 | Controllers::CpuAcct 83 | } 84 | } 85 | 86 | impl<'a> From<&'a Subsystem> for &'a CpuAcctController { 87 | fn from(sub: &'a Subsystem) -> &'a CpuAcctController { 88 | unsafe { 89 | match sub { 90 | Subsystem::CpuAcct(c) => c, 91 | _ => { 92 | assert_eq!(1, 0); 93 | let v = std::mem::MaybeUninit::uninit(); 94 | v.assume_init() 95 | } 96 | } 97 | } 98 | } 99 | } 100 | 101 | impl CpuAcctController { 102 | /// Contructs a new `CpuAcctController` with `root` serving as the root of the control group. 103 | pub fn new(point: PathBuf, root: PathBuf) -> Self { 104 | Self { 105 | base: root, 106 | path: point, 107 | } 108 | } 109 | 110 | /// Gathers the statistics that are available in the control group into a `CpuAcct` structure. 111 | pub fn cpuacct(&self) -> CpuAcct { 112 | CpuAcct { 113 | stat: self 114 | .open_path("cpuacct.stat", false) 115 | .and_then(read_string_from) 116 | .unwrap_or_default(), 117 | usage: self 118 | .open_path("cpuacct.usage", false) 119 | .and_then(read_u64_from) 120 | .unwrap_or(0), 121 | usage_all: self 122 | .open_path("cpuacct.usage_all", false) 123 | .and_then(read_string_from) 124 | .unwrap_or_default(), 125 | usage_percpu: self 126 | .open_path("cpuacct.usage_percpu", false) 127 | .and_then(read_string_from) 128 | .unwrap_or_default(), 129 | usage_percpu_sys: self 130 | .open_path("cpuacct.usage_percpu_sys", false) 131 | .and_then(read_string_from) 132 | .unwrap_or_default(), 133 | usage_percpu_user: self 134 | .open_path("cpuacct.usage_percpu_user", false) 135 | .and_then(read_string_from) 136 | .unwrap_or_default(), 137 | usage_sys: self 138 | .open_path("cpuacct.usage_sys", false) 139 | .and_then(read_u64_from) 140 | .unwrap_or(0), 141 | usage_user: self 142 | .open_path("cpuacct.usage_user", false) 143 | .and_then(read_u64_from) 144 | .unwrap_or(0), 145 | } 146 | } 147 | 148 | /// Reset the statistics the kernel has gathered about the control group. 149 | pub fn reset(&self) -> Result<()> { 150 | self.open_path("cpuacct.usage", true).and_then(|mut file| { 151 | file.write_all(b"0").map_err(|e| { 152 | Error::with_cause(WriteFailed("cpuacct.usage".to_string(), "0".to_string()), e) 153 | }) 154 | }) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/fs/devices.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | //! This module contains the implementation of the `devices` cgroup subsystem. 7 | //! 8 | //! See the Kernel's documentation for more information about this subsystem, found at: 9 | //! [Documentation/cgroup-v1/devices.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/devices.txt) 10 | use std::io::{Read, Write}; 11 | use std::path::PathBuf; 12 | 13 | use log::*; 14 | 15 | use crate::fs::error::ErrorKind::*; 16 | use crate::fs::error::*; 17 | 18 | use crate::fs::{ 19 | ControllIdentifier, ControllerInternal, Controllers, DeviceResource, DeviceResources, 20 | Resources, Subsystem, 21 | }; 22 | 23 | /// A controller that allows controlling the `devices` subsystem of a Cgroup. 24 | /// 25 | /// In essence, using the devices controller, it is possible to allow or disallow sets of devices to 26 | /// be used by the control group's tasks. 27 | #[derive(Debug, Clone)] 28 | pub struct DevicesController { 29 | base: PathBuf, 30 | path: PathBuf, 31 | } 32 | 33 | /// An enum holding the different types of devices that can be manipulated using this controller. 34 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 35 | #[cfg_attr( 36 | feature = "serde", 37 | derive(serde::Serialize, serde::Deserialize), 38 | serde(rename_all = "snake_case") 39 | )] 40 | pub enum DeviceType { 41 | /// The rule applies to all devices. 42 | All, 43 | /// The rule only applies to character devices. 44 | Char, 45 | /// The rule only applies to block devices. 46 | Block, 47 | } 48 | 49 | #[allow(clippy::derivable_impls)] 50 | impl Default for DeviceType { 51 | fn default() -> Self { 52 | DeviceType::All 53 | } 54 | } 55 | 56 | impl DeviceType { 57 | /// Convert a DeviceType into the character that the kernel recognizes. 58 | #[allow(clippy::should_implement_trait, clippy::wrong_self_convention)] 59 | pub fn to_char(&self) -> char { 60 | match self { 61 | DeviceType::All => 'a', 62 | DeviceType::Char => 'c', 63 | DeviceType::Block => 'b', 64 | } 65 | } 66 | 67 | /// Convert the kenrel's representation into the DeviceType type. 68 | pub fn from_char(c: Option) -> Option { 69 | match c { 70 | Some('a') => Some(DeviceType::All), 71 | Some('c') => Some(DeviceType::Char), 72 | Some('b') => Some(DeviceType::Block), 73 | _ => None, 74 | } 75 | } 76 | } 77 | 78 | /// An enum with the permissions that can be allowed/denied to the control group. 79 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 80 | #[cfg_attr( 81 | feature = "serde", 82 | derive(serde::Serialize, serde::Deserialize), 83 | serde(rename_all = "snake_case") 84 | )] 85 | pub enum DevicePermissions { 86 | /// Permission to read from the device. 87 | Read, 88 | /// Permission to write to the device. 89 | Write, 90 | /// Permission to execute the `mknod(2)` system call with the device's major and minor numbers. 91 | /// That is, the permission to create a special file that refers to the device node. 92 | MkNod, 93 | } 94 | 95 | impl DevicePermissions { 96 | /// Convert a DevicePermissions into the character that the kernel recognizes. 97 | #[allow(clippy::should_implement_trait, clippy::wrong_self_convention)] 98 | pub fn to_char(&self) -> char { 99 | match self { 100 | DevicePermissions::Read => 'r', 101 | DevicePermissions::Write => 'w', 102 | DevicePermissions::MkNod => 'm', 103 | } 104 | } 105 | 106 | /// Convert a char to a DevicePermission if there is such a mapping. 107 | pub fn from_char(c: char) -> Option { 108 | match c { 109 | 'r' => Some(DevicePermissions::Read), 110 | 'w' => Some(DevicePermissions::Write), 111 | 'm' => Some(DevicePermissions::MkNod), 112 | _ => None, 113 | } 114 | } 115 | 116 | /// Checks whether the string is a valid descriptor of DevicePermissions. 117 | pub fn is_valid(s: &str) -> bool { 118 | if s.is_empty() { 119 | return false; 120 | } 121 | for i in s.chars() { 122 | if i != 'r' && i != 'w' && i != 'm' { 123 | return false; 124 | } 125 | } 126 | true 127 | } 128 | 129 | /// Returns a Vec will all the permissions that a device can have. 130 | pub fn all() -> Vec { 131 | vec![ 132 | DevicePermissions::Read, 133 | DevicePermissions::Write, 134 | DevicePermissions::MkNod, 135 | ] 136 | } 137 | 138 | /// Convert a string into DevicePermissions. 139 | #[allow(clippy::should_implement_trait)] 140 | pub fn from_str(s: &str) -> Result> { 141 | let mut v = Vec::new(); 142 | if s.is_empty() { 143 | return Ok(v); 144 | } 145 | for e in s.chars() { 146 | let perm = DevicePermissions::from_char(e).ok_or_else(|| Error::new(ParseError))?; 147 | v.push(perm); 148 | } 149 | 150 | Ok(v) 151 | } 152 | } 153 | 154 | impl ControllerInternal for DevicesController { 155 | fn control_type(&self) -> Controllers { 156 | Controllers::Devices 157 | } 158 | fn get_path(&self) -> &PathBuf { 159 | &self.path 160 | } 161 | fn get_path_mut(&mut self) -> &mut PathBuf { 162 | &mut self.path 163 | } 164 | fn get_base(&self) -> &PathBuf { 165 | &self.base 166 | } 167 | 168 | fn apply(&self, res: &Resources) -> Result<()> { 169 | // get the resources that apply to this controller 170 | let res: &DeviceResources = &res.devices; 171 | 172 | for i in &res.devices { 173 | if i.allow { 174 | self.allow_device(i.devtype, i.major, i.minor, &i.access)?; 175 | } else { 176 | self.deny_device(i.devtype, i.major, i.minor, &i.access)?; 177 | } 178 | } 179 | 180 | Ok(()) 181 | } 182 | } 183 | 184 | impl ControllIdentifier for DevicesController { 185 | fn controller_type() -> Controllers { 186 | Controllers::Devices 187 | } 188 | } 189 | 190 | impl<'a> From<&'a Subsystem> for &'a DevicesController { 191 | fn from(sub: &'a Subsystem) -> &'a DevicesController { 192 | unsafe { 193 | match sub { 194 | Subsystem::Devices(c) => c, 195 | _ => { 196 | assert_eq!(1, 0); 197 | let v = std::mem::MaybeUninit::uninit(); 198 | v.assume_init() 199 | } 200 | } 201 | } 202 | } 203 | } 204 | 205 | impl DevicesController { 206 | /// Constructs a new `DevicesController` with `root` serving as the root of the control group. 207 | pub fn new(point: PathBuf, root: PathBuf) -> Self { 208 | Self { 209 | base: root, 210 | path: point, 211 | } 212 | } 213 | 214 | /// Allow a (possibly, set of) device(s) to be used by the tasks in the control group. 215 | /// 216 | /// When `-1` is passed as `major` or `minor`, the kernel interprets that value as "any", 217 | /// meaning that it will match any device. 218 | pub fn allow_device( 219 | &self, 220 | devtype: DeviceType, 221 | major: i64, 222 | minor: i64, 223 | perm: &[DevicePermissions], 224 | ) -> Result<()> { 225 | let perms = perm 226 | .iter() 227 | .map(DevicePermissions::to_char) 228 | .collect::(); 229 | let minor = if minor == -1 { 230 | "*".to_string() 231 | } else { 232 | format!("{}", minor) 233 | }; 234 | let major = if major == -1 { 235 | "*".to_string() 236 | } else { 237 | format!("{}", major) 238 | }; 239 | let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms); 240 | self.open_path("devices.allow", true).and_then(|mut file| { 241 | file.write_all(final_str.as_ref()).map_err(|e| { 242 | Error::with_cause( 243 | WriteFailed( 244 | self.get_path().join("devices.allow").display().to_string(), 245 | final_str, 246 | ), 247 | e, 248 | ) 249 | }) 250 | }) 251 | } 252 | 253 | /// Deny the control group's tasks access to the devices covered by `dev`. 254 | /// 255 | /// When `-1` is passed as `major` or `minor`, the kernel interprets that value as "any", 256 | /// meaning that it will match any device. 257 | pub fn deny_device( 258 | &self, 259 | devtype: DeviceType, 260 | major: i64, 261 | minor: i64, 262 | perm: &[DevicePermissions], 263 | ) -> Result<()> { 264 | let perms = perm 265 | .iter() 266 | .map(DevicePermissions::to_char) 267 | .collect::(); 268 | let minor = if minor == -1 { 269 | "*".to_string() 270 | } else { 271 | format!("{}", minor) 272 | }; 273 | let major = if major == -1 { 274 | "*".to_string() 275 | } else { 276 | format!("{}", major) 277 | }; 278 | let final_str = format!("{} {}:{} {}", devtype.to_char(), major, minor, perms); 279 | self.open_path("devices.deny", true).and_then(|mut file| { 280 | file.write_all(final_str.as_ref()).map_err(|e| { 281 | Error::with_cause( 282 | WriteFailed( 283 | self.get_path().join("devices.deny").display().to_string(), 284 | final_str, 285 | ), 286 | e, 287 | ) 288 | }) 289 | }) 290 | } 291 | 292 | /// Get the current list of allowed devices. 293 | pub fn allowed_devices(&self) -> Result> { 294 | self.open_path("devices.list", false).and_then(|mut file| { 295 | let mut s = String::new(); 296 | let res = file.read_to_string(&mut s); 297 | match res { 298 | Ok(_) => s 299 | .lines() 300 | .map(|line| parse_device_line(line, true)) 301 | .collect(), 302 | Err(e) => Err(Error::with_cause(ReadFailed("devices.list".to_string()), e)), 303 | } 304 | }) 305 | } 306 | } 307 | 308 | fn parse_device_number(s: &str) -> Result { 309 | if s == "*" { 310 | Ok(-1) 311 | } else { 312 | s.parse::().map_err(|_| Error::new(ParseError)) 313 | } 314 | } 315 | 316 | fn parse_device_line(line: &str, allow: bool) -> Result { 317 | let parts: Vec<&str> = line.split([' ', ':']).collect(); 318 | if parts.len() != 4 { 319 | error!("allowed_devices: invalid line format: {:?}", line); 320 | return Err(Error::new(ParseError)); 321 | } 322 | 323 | let devtype = DeviceType::from_char(parts[0].chars().next()).ok_or_else(|| { 324 | error!("allowed_devices: invalid device type: {:?}", parts[0]); 325 | Error::new(ParseError) 326 | })?; 327 | let major = parse_device_number(parts[1]).inspect_err(|_| { 328 | error!("allowed_devices: invalid major number: {:?}", parts[1]); 329 | })?; 330 | let minor = parse_device_number(parts[2]).inspect_err(|_| { 331 | error!("allowed_devices: invalid minor number: {:?}", parts[2]); 332 | })?; 333 | let access = DevicePermissions::from_str(parts[3])?; 334 | 335 | Ok(DeviceResource { 336 | allow, 337 | devtype, 338 | major, 339 | minor, 340 | access, 341 | }) 342 | } 343 | -------------------------------------------------------------------------------- /src/fs/error.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 | use std::error::Error as StdError; 8 | use std::fmt; 9 | 10 | /// The different types of errors that can occur while manipulating control groups. 11 | #[derive(thiserror::Error, Debug, Eq, PartialEq)] 12 | pub enum ErrorKind { 13 | #[error("fs error")] 14 | FsError, 15 | 16 | #[error("common error: {0}")] 17 | Common(String), 18 | 19 | /// An error occured while writing to a control group file. 20 | #[error("unable to write to a control group file {0}, value {1}")] 21 | WriteFailed(String, String), 22 | 23 | /// An error occured while trying to read from a control group file. 24 | #[error("unable to read a control group file {0}")] 25 | ReadFailed(String), 26 | 27 | /// An error occured while trying to remove a control group. 28 | #[error("unable to remove a control group")] 29 | RemoveFailed, 30 | 31 | /// An error occured while trying to parse a value from a control group file. 32 | /// 33 | /// In the future, there will be some information attached to this field. 34 | #[error("unable to parse control group file")] 35 | ParseError, 36 | 37 | /// You tried to do something invalid. 38 | /// 39 | /// This could be because you tried to set a value in a control group that is not a root 40 | /// control group. Or, when using unified hierarchy, you tried to add a task in a leaf node. 41 | #[error("the requested operation is invalid")] 42 | InvalidOperation, 43 | 44 | /// The path of the control group was invalid. 45 | /// 46 | /// This could be caused by trying to escape the control group filesystem via a string of "..". 47 | /// This crate checks against this and operations will fail with this error. 48 | #[error("the given path is invalid")] 49 | InvalidPath, 50 | 51 | #[error("invalid bytes size")] 52 | InvalidBytesSize, 53 | 54 | /// The specified controller is not in the list of supported controllers. 55 | #[error("specified controller is not in the list of supported controllers")] 56 | SpecifiedControllers, 57 | 58 | /// Using method in wrong cgroup version. 59 | #[error("using method in wrong cgroup version")] 60 | CgroupVersion, 61 | 62 | /// Using method in wrong cgroup mode. 63 | #[error("using method in wrong cgroup mode.")] 64 | CgroupMode, 65 | 66 | /// Subsystems is empty. 67 | #[error("subsystems is empty")] 68 | SubsystemsEmpty, 69 | 70 | /// An unknown error has occured. 71 | #[error("an unknown error")] 72 | Other, 73 | } 74 | 75 | #[derive(Debug)] 76 | pub struct Error { 77 | kind: ErrorKind, 78 | cause: Option>, 79 | } 80 | 81 | impl fmt::Display for Error { 82 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 83 | if let Some(cause) = &self.cause { 84 | write!(f, "{} caused by: {:?}", &self.kind, cause) 85 | } else { 86 | write!(f, "{}", &self.kind) 87 | } 88 | } 89 | } 90 | 91 | impl StdError for Error { 92 | fn source(&self) -> Option<&(dyn StdError + 'static)> { 93 | #[allow(clippy::manual_map)] 94 | match self.cause { 95 | Some(ref x) => Some(&**x), 96 | None => None, 97 | } 98 | } 99 | } 100 | 101 | impl Error { 102 | pub(crate) fn from_string(s: String) -> Self { 103 | Self { 104 | kind: ErrorKind::Common(s), 105 | cause: None, 106 | } 107 | } 108 | pub(crate) fn new(kind: ErrorKind) -> Self { 109 | Self { kind, cause: None } 110 | } 111 | 112 | pub(crate) fn with_cause(kind: ErrorKind, cause: E) -> Self 113 | where 114 | E: 'static + Send + Sync + StdError, 115 | { 116 | Self { 117 | kind, 118 | cause: Some(Box::new(cause)), 119 | } 120 | } 121 | 122 | pub fn kind(&self) -> &ErrorKind { 123 | &self.kind 124 | } 125 | } 126 | 127 | pub type Result = ::std::result::Result; 128 | -------------------------------------------------------------------------------- /src/fs/events.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Ant Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | use eventfd::{eventfd, EfdFlags}; 7 | use nix::sys::eventfd; 8 | use std::fs::{self, File}; 9 | use std::io::Read; 10 | use std::os::unix::io::{AsRawFd, FromRawFd}; 11 | use std::path::Path; 12 | use std::sync::mpsc::{self, Receiver}; 13 | use std::thread; 14 | 15 | use crate::fs::error::ErrorKind::*; 16 | use crate::fs::error::*; 17 | 18 | // notify_on_oom returns channel on which you can expect event about OOM, 19 | // if process died without OOM this channel will be closed. 20 | pub fn notify_on_oom_v2(key: &str, dir: &Path) -> Result> { 21 | register_memory_event(key, dir, "memory.oom_control", "") 22 | } 23 | 24 | // notify_on_oom returns channel on which you can expect event about OOM, 25 | // if process died without OOM this channel will be closed. 26 | pub fn notify_on_oom_v1(key: &str, dir: &Path) -> Result> { 27 | register_memory_event(key, dir, "memory.oom_control", "") 28 | } 29 | 30 | // level is one of "low", "medium", or "critical" 31 | pub fn notify_memory_pressure(key: &str, dir: &Path, level: &str) -> Result> { 32 | if level != "low" && level != "medium" && level != "critical" { 33 | return Err(Error::from_string(format!( 34 | "invalid pressure level {}", 35 | level 36 | ))); 37 | } 38 | 39 | register_memory_event(key, dir, "memory.pressure_level", level) 40 | } 41 | 42 | fn register_memory_event( 43 | key: &str, 44 | cg_dir: &Path, 45 | event_name: &str, 46 | arg: &str, 47 | ) -> Result> { 48 | let path = cg_dir.join(event_name); 49 | let event_file = File::open(path.clone()) 50 | .map_err(|e| Error::with_cause(ReadFailed(path.display().to_string()), e))?; 51 | 52 | let eventfd = eventfd(0, EfdFlags::EFD_CLOEXEC) 53 | .map_err(|e| Error::with_cause(ReadFailed("eventfd".to_string()), e))?; 54 | 55 | let event_control_path = cg_dir.join("cgroup.event_control"); 56 | let data = if arg.is_empty() { 57 | format!("{} {}", eventfd, event_file.as_raw_fd()) 58 | } else { 59 | format!("{} {} {}", eventfd, event_file.as_raw_fd(), arg) 60 | }; 61 | 62 | // write to file and set mode to 0700(FIXME) 63 | fs::write(&event_control_path, data.clone()).map_err(|e| { 64 | Error::with_cause( 65 | WriteFailed(event_control_path.display().to_string(), data), 66 | e, 67 | ) 68 | })?; 69 | 70 | let mut eventfd_file = unsafe { File::from_raw_fd(eventfd) }; 71 | 72 | let (sender, receiver) = mpsc::channel(); 73 | let key = key.to_string(); 74 | 75 | thread::spawn(move || { 76 | loop { 77 | let mut buf = [0; 8]; 78 | if eventfd_file.read(&mut buf).is_err() { 79 | return; 80 | } 81 | 82 | // When a cgroup is destroyed, an event is sent to eventfd. 83 | // So if the control path is gone, return instead of notifying. 84 | if !Path::new(&event_control_path).exists() { 85 | return; 86 | } 87 | sender.send(key.clone()).unwrap(); 88 | } 89 | }); 90 | 91 | Ok(receiver) 92 | } 93 | -------------------------------------------------------------------------------- /src/fs/freezer.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 Ant Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! This module contains the implementation of the `freezer` cgroup subsystem. 8 | //! 9 | //! See the Kernel's documentation for more information about this subsystem, found at: 10 | //! [Documentation/cgroup-v1/freezer-subsystem.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt) 11 | use std::io::{Read, Write}; 12 | use std::path::PathBuf; 13 | 14 | use crate::fs::error::ErrorKind::*; 15 | use crate::fs::error::*; 16 | use crate::fs::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; 17 | use crate::FreezerState; 18 | 19 | /// A controller that allows controlling the `freezer` subsystem of a Cgroup. 20 | /// 21 | /// In essence, this subsystem allows the user to freeze and thaw (== "un-freeze") the processes in 22 | /// the control group. This is done _transparently_ so that neither the parent, nor the children of 23 | /// the processes can observe the freeze. 24 | /// 25 | /// Note that if the control group is currently in the `Frozen` or `Freezing` state, then no 26 | /// processes can be added to it. 27 | #[derive(Debug, Clone)] 28 | pub struct FreezerController { 29 | base: PathBuf, 30 | path: PathBuf, 31 | v2: bool, 32 | } 33 | 34 | impl ControllerInternal for FreezerController { 35 | fn control_type(&self) -> Controllers { 36 | Controllers::Freezer 37 | } 38 | fn get_path(&self) -> &PathBuf { 39 | &self.path 40 | } 41 | fn get_path_mut(&mut self) -> &mut PathBuf { 42 | &mut self.path 43 | } 44 | fn get_base(&self) -> &PathBuf { 45 | &self.base 46 | } 47 | 48 | fn apply(&self, _res: &Resources) -> Result<()> { 49 | Ok(()) 50 | } 51 | } 52 | 53 | impl ControllIdentifier for FreezerController { 54 | fn controller_type() -> Controllers { 55 | Controllers::Freezer 56 | } 57 | } 58 | 59 | impl<'a> From<&'a Subsystem> for &'a FreezerController { 60 | fn from(sub: &'a Subsystem) -> &'a FreezerController { 61 | unsafe { 62 | match sub { 63 | Subsystem::Freezer(c) => c, 64 | _ => { 65 | assert_eq!(1, 0); 66 | let v = std::mem::MaybeUninit::uninit(); 67 | v.assume_init() 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 74 | impl FreezerController { 75 | /// Contructs a new `FreezerController` with `root` serving as the root of the control group. 76 | pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self { 77 | Self { 78 | base: root, 79 | path: point, 80 | v2, 81 | } 82 | } 83 | /// Freezes the processes in the control group. 84 | pub fn freeze(&self) -> Result<()> { 85 | let mut file_name = "freezer.state"; 86 | let mut content = "FROZEN".to_string(); 87 | if self.v2 { 88 | file_name = "cgroup.freeze"; 89 | content = "1".to_string(); 90 | } 91 | 92 | self.open_path(file_name, true).and_then(|mut file| { 93 | file.write_all(content.as_ref()) 94 | .map_err(|e| Error::with_cause(WriteFailed(file_name.to_string(), content), e)) 95 | }) 96 | } 97 | 98 | /// Thaws, that is, unfreezes the processes in the control group. 99 | pub fn thaw(&self) -> Result<()> { 100 | let mut file_name = "freezer.state"; 101 | let mut content = "THAWED".to_string(); 102 | if self.v2 { 103 | file_name = "cgroup.freeze"; 104 | content = "0".to_string(); 105 | } 106 | self.open_path(file_name, true).and_then(|mut file| { 107 | file.write_all(content.as_ref()) 108 | .map_err(|e| Error::with_cause(WriteFailed(file_name.to_string(), content), e)) 109 | }) 110 | } 111 | 112 | /// Retrieve the state of processes in the control group. 113 | pub fn state(&self) -> Result { 114 | let mut file_name = "freezer.state"; 115 | if self.v2 { 116 | file_name = "cgroup.freeze"; 117 | } 118 | self.open_path(file_name, false).and_then(|mut file| { 119 | let mut s = String::new(); 120 | let res = file.read_to_string(&mut s); 121 | match res { 122 | Ok(_) => match s.trim() { 123 | "FROZEN" => Ok(FreezerState::Frozen), 124 | "THAWED" => Ok(FreezerState::Thawed), 125 | "1" => Ok(FreezerState::Frozen), 126 | "0" => Ok(FreezerState::Thawed), 127 | "FREEZING" => Ok(FreezerState::Freezing), 128 | _ => Err(Error::new(ParseError)), 129 | }, 130 | Err(e) => Err(Error::with_cause(ReadFailed(file_name.to_string()), e)), 131 | } 132 | }) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/fs/hierarchies.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 Ant Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! This module represents the various control group hierarchies the Linux kernel supports. 8 | 9 | use std::fs; 10 | use std::fs::File; 11 | use std::io::{BufRead, BufReader}; 12 | use std::path::{Path, PathBuf}; 13 | 14 | use crate::fs::blkio::BlkIoController; 15 | use crate::fs::cpu::CpuController; 16 | use crate::fs::cpuacct::CpuAcctController; 17 | use crate::fs::cpuset::CpuSetController; 18 | use crate::fs::devices::DevicesController; 19 | use crate::fs::freezer::FreezerController; 20 | use crate::fs::hugetlb::HugeTlbController; 21 | use crate::fs::memory::MemController; 22 | use crate::fs::net_cls::NetClsController; 23 | use crate::fs::net_prio::NetPrioController; 24 | use crate::fs::perf_event::PerfEventController; 25 | use crate::fs::pid::PidController; 26 | use crate::fs::rdma::RdmaController; 27 | use crate::fs::systemd::SystemdController; 28 | use crate::fs::{Controllers, Hierarchy, Subsystem}; 29 | 30 | use crate::fs::cgroup::Cgroup; 31 | 32 | /// Process mounts information. 33 | /// 34 | /// See `proc(5)` for format details. 35 | #[derive(Debug, PartialEq, Eq, Hash, Clone)] 36 | pub struct Mountinfo { 37 | /// Mount root directory of the file system. 38 | pub mount_root: PathBuf, 39 | /// Mount pathname relative to the process's root. 40 | pub mount_point: PathBuf, 41 | /// Filesystem type (main type with optional sub-type). 42 | pub fs_type: (String, Option), 43 | /// Superblock options. 44 | pub super_opts: Vec, 45 | } 46 | 47 | pub(crate) fn parse_mountinfo_for_line(line: &str) -> Option { 48 | let s_values: Vec<_> = line.split(" - ").collect(); 49 | if s_values.len() != 2 { 50 | return None; 51 | } 52 | 53 | let s0_values: Vec<_> = s_values[0].trim().split(' ').collect(); 54 | let s1_values: Vec<_> = s_values[1].trim().split(' ').collect(); 55 | if s0_values.len() < 6 || s1_values.len() < 3 { 56 | return None; 57 | } 58 | let mount_point = PathBuf::from(s0_values[4]); 59 | let mount_root = PathBuf::from(s0_values[3]); 60 | let fs_type_values: Vec<_> = s1_values[0].trim().split('.').collect(); 61 | let fs_type = match fs_type_values.len() { 62 | 1 => (fs_type_values[0].to_string(), None), 63 | 2 => ( 64 | fs_type_values[0].to_string(), 65 | Some(fs_type_values[1].to_string()), 66 | ), 67 | _ => return None, 68 | }; 69 | 70 | let super_opts: Vec = s1_values[2].trim().split(',').map(String::from).collect(); 71 | Some(Mountinfo { 72 | mount_root, 73 | mount_point, 74 | fs_type, 75 | super_opts, 76 | }) 77 | } 78 | 79 | /// Parses the provided mountinfo file. 80 | fn mountinfo_file(file: &mut File) -> Vec { 81 | let mut r = Vec::new(); 82 | for line in BufReader::new(file).lines() { 83 | match line { 84 | Ok(line) => { 85 | if let Some(mi) = parse_mountinfo_for_line(&line) { 86 | if mi.fs_type.0 == "cgroup" { 87 | r.push(mi); 88 | } 89 | } 90 | } 91 | Err(_) => break, 92 | } 93 | } 94 | r 95 | } 96 | 97 | /// Returns mounts information for the current process. 98 | pub fn mountinfo_self() -> Vec { 99 | match File::open("/proc/self/mountinfo") { 100 | Ok(mut file) => mountinfo_file(&mut file), 101 | Err(_) => vec![], 102 | } 103 | } 104 | 105 | /// The standard, original cgroup implementation. Often referred to as "cgroupv1". 106 | #[derive(Debug, Clone)] 107 | pub struct V1 { 108 | mountinfo: Vec, 109 | } 110 | 111 | #[derive(Debug, Clone)] 112 | pub struct V2 { 113 | root: String, 114 | } 115 | 116 | impl Hierarchy for V1 { 117 | fn v2(&self) -> bool { 118 | false 119 | } 120 | 121 | fn subsystems(&self) -> Vec { 122 | let mut subs = vec![]; 123 | 124 | // The cgroup writeback feature requires cooperation between memcgs and blkcgs 125 | // To avoid exceptions, we should add_task for blkcg before memcg(push BlkIo before Mem) 126 | // For more Information: https://www.alibabacloud.com/help/doc-detail/155509.htm 127 | if let Some((point, root)) = self.get_mount_point(Controllers::BlkIo) { 128 | subs.push(Subsystem::BlkIo(BlkIoController::new(point, root, false))); 129 | } 130 | if let Some((point, root)) = self.get_mount_point(Controllers::Mem) { 131 | subs.push(Subsystem::Mem(MemController::new(point, root, false))); 132 | } 133 | if let Some((point, root)) = self.get_mount_point(Controllers::Pids) { 134 | subs.push(Subsystem::Pid(PidController::new(point, root, false))); 135 | } 136 | if let Some((point, root)) = self.get_mount_point(Controllers::CpuSet) { 137 | subs.push(Subsystem::CpuSet(CpuSetController::new(point, root, false))); 138 | } 139 | if let Some((point, root)) = self.get_mount_point(Controllers::CpuAcct) { 140 | subs.push(Subsystem::CpuAcct(CpuAcctController::new(point, root))); 141 | } 142 | if let Some((point, root)) = self.get_mount_point(Controllers::Cpu) { 143 | subs.push(Subsystem::Cpu(CpuController::new(point, root, false))); 144 | } 145 | if let Some((point, root)) = self.get_mount_point(Controllers::Devices) { 146 | subs.push(Subsystem::Devices(DevicesController::new(point, root))); 147 | } 148 | if let Some((point, root)) = self.get_mount_point(Controllers::Freezer) { 149 | subs.push(Subsystem::Freezer(FreezerController::new( 150 | point, root, false, 151 | ))); 152 | } 153 | if let Some((point, root)) = self.get_mount_point(Controllers::NetCls) { 154 | subs.push(Subsystem::NetCls(NetClsController::new(point, root))); 155 | } 156 | if let Some((point, root)) = self.get_mount_point(Controllers::PerfEvent) { 157 | subs.push(Subsystem::PerfEvent(PerfEventController::new(point, root))); 158 | } 159 | if let Some((point, root)) = self.get_mount_point(Controllers::NetPrio) { 160 | subs.push(Subsystem::NetPrio(NetPrioController::new(point, root))); 161 | } 162 | if let Some((point, root)) = self.get_mount_point(Controllers::HugeTlb) { 163 | subs.push(Subsystem::HugeTlb(HugeTlbController::new( 164 | point, root, false, 165 | ))); 166 | } 167 | if let Some((point, root)) = self.get_mount_point(Controllers::Rdma) { 168 | subs.push(Subsystem::Rdma(RdmaController::new(point, root))); 169 | } 170 | if let Some((point, root)) = self.get_mount_point(Controllers::Systemd) { 171 | subs.push(Subsystem::Systemd(SystemdController::new( 172 | point, root, false, 173 | ))); 174 | } 175 | 176 | subs 177 | } 178 | 179 | fn root_control_group(&self) -> Cgroup { 180 | Cgroup::load(auto(), "") 181 | } 182 | 183 | fn parent_control_group(&self, path: &str) -> Cgroup { 184 | let path = Path::new(path); 185 | let parent_path = path.parent().unwrap().to_string_lossy().to_string(); 186 | Cgroup::load(auto(), parent_path) 187 | } 188 | 189 | fn root(&self) -> PathBuf { 190 | self.mountinfo 191 | .iter() 192 | .find_map(|m| { 193 | if m.fs_type.0 == "cgroup" { 194 | return Some(m.mount_point.parent().unwrap()); 195 | } 196 | None 197 | }) 198 | .unwrap() 199 | .to_path_buf() 200 | } 201 | } 202 | 203 | impl Hierarchy for V2 { 204 | fn v2(&self) -> bool { 205 | true 206 | } 207 | 208 | fn subsystems(&self) -> Vec { 209 | let p = format!("{}/{}", UNIFIED_MOUNTPOINT, "cgroup.controllers"); 210 | let ret = fs::read_to_string(p.as_str()); 211 | if ret.is_err() { 212 | return vec![]; 213 | } 214 | 215 | let mut subs = vec![]; 216 | 217 | let controllers = ret.unwrap().trim().to_string(); 218 | let mut controller_list: Vec<&str> = controllers.split(' ').collect(); 219 | 220 | // The freezer functionality is present in V2, but not as a controller, 221 | // but apparently as a core functionality. FreezerController supports 222 | // that, but we must explicitly fake the controller here. 223 | controller_list.push("freezer"); 224 | 225 | for s in controller_list { 226 | match s { 227 | "cpu" => { 228 | subs.push(Subsystem::Cpu(CpuController::new( 229 | self.root(), 230 | PathBuf::from(""), 231 | true, 232 | ))); 233 | } 234 | "io" => { 235 | subs.push(Subsystem::BlkIo(BlkIoController::new( 236 | self.root(), 237 | PathBuf::from(""), 238 | true, 239 | ))); 240 | } 241 | "cpuset" => { 242 | subs.push(Subsystem::CpuSet(CpuSetController::new( 243 | self.root(), 244 | PathBuf::from(""), 245 | true, 246 | ))); 247 | } 248 | "memory" => { 249 | subs.push(Subsystem::Mem(MemController::new( 250 | self.root(), 251 | PathBuf::from(""), 252 | true, 253 | ))); 254 | } 255 | "pids" => { 256 | subs.push(Subsystem::Pid(PidController::new( 257 | self.root(), 258 | PathBuf::from(""), 259 | true, 260 | ))); 261 | } 262 | "freezer" => { 263 | subs.push(Subsystem::Freezer(FreezerController::new( 264 | self.root(), 265 | PathBuf::from(""), 266 | true, 267 | ))); 268 | } 269 | "hugetlb" => { 270 | subs.push(Subsystem::HugeTlb(HugeTlbController::new( 271 | self.root(), 272 | PathBuf::from(""), 273 | true, 274 | ))); 275 | } 276 | _ => {} 277 | } 278 | } 279 | 280 | subs 281 | } 282 | 283 | fn root_control_group(&self) -> Cgroup { 284 | Cgroup::load(auto(), "") 285 | } 286 | 287 | fn parent_control_group(&self, path: &str) -> Cgroup { 288 | let path = Path::new(path); 289 | let parent_path = path.parent().unwrap().to_string_lossy().to_string(); 290 | Cgroup::load(auto(), parent_path) 291 | } 292 | 293 | fn root(&self) -> PathBuf { 294 | PathBuf::from(self.root.clone()) 295 | } 296 | } 297 | 298 | impl V1 { 299 | /// Finds where control groups are mounted to and returns a hierarchy in which control groups 300 | /// can be created. 301 | pub fn new() -> V1 { 302 | V1 { 303 | mountinfo: mountinfo_self(), 304 | } 305 | } 306 | 307 | pub fn get_mount_point(&self, controller: Controllers) -> Option<(PathBuf, PathBuf)> { 308 | self.mountinfo.iter().find_map(|m| { 309 | if m.fs_type.0 == "cgroup" && m.super_opts.contains(&controller.to_string()) { 310 | return Some((m.mount_point.to_owned(), m.mount_root.to_owned())); 311 | } 312 | None 313 | }) 314 | } 315 | } 316 | 317 | impl Default for V1 { 318 | fn default() -> Self { 319 | Self::new() 320 | } 321 | } 322 | 323 | impl V2 { 324 | /// Finds where control groups are mounted to and returns a hierarchy in which control groups 325 | /// can be created. 326 | pub fn new() -> V2 { 327 | V2 { 328 | root: String::from(UNIFIED_MOUNTPOINT), 329 | } 330 | } 331 | } 332 | 333 | impl Default for V2 { 334 | fn default() -> Self { 335 | Self::new() 336 | } 337 | } 338 | 339 | pub const UNIFIED_MOUNTPOINT: &str = "/sys/fs/cgroup"; 340 | 341 | pub fn is_cgroup2_unified_mode() -> bool { 342 | use nix::sys::statfs; 343 | 344 | let path = std::path::Path::new(UNIFIED_MOUNTPOINT); 345 | let fs_stat = match statfs::statfs(path) { 346 | Ok(fs_stat) => fs_stat, 347 | Err(_) => return false, 348 | }; 349 | 350 | fs_stat.filesystem_type() == statfs::CGROUP2_SUPER_MAGIC 351 | } 352 | 353 | pub fn auto() -> Box { 354 | if is_cgroup2_unified_mode() { 355 | Box::new(V2::new()) 356 | } else { 357 | Box::new(V1::new()) 358 | } 359 | } 360 | 361 | #[cfg(test)] 362 | mod tests { 363 | use super::*; 364 | 365 | #[test] 366 | fn test_parse_mount() { 367 | let mountinfo = vec![ 368 | ("29 26 0:26 / /sys/fs/cgroup/cpuset,cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:10 - cgroup cgroup rw,cpuset,cpu,cpuacct", 369 | Mountinfo{mount_root: PathBuf::from("/"), mount_point: PathBuf::from("/sys/fs/cgroup/cpuset,cpu,cpuacct"), fs_type: ("cgroup".to_string(), None), super_opts: vec![ 370 | "rw".to_string(), 371 | "cpuset".to_string(), 372 | "cpu".to_string(), 373 | "cpuacct".to_string(), 374 | ]}), 375 | ("121 1731 0:42 / /shm rw,nosuid,nodev,noexec,relatime shared:68 master:66 - tmpfs shm rw,size=65536k", 376 | Mountinfo{mount_root: PathBuf::from("/"), mount_point: PathBuf::from("/shm"), fs_type: ("tmpfs".to_string(), None), super_opts: vec![ 377 | "rw".to_string(), 378 | "size=65536k".to_string(), 379 | ]}), 380 | ("121 1731 0:42 / /shm rw,nosuid,nodev,noexec,relatime shared:68 master:66 - tmpfs.123 shm rw,size=65536k", 381 | Mountinfo{mount_root: PathBuf::from("/"), mount_point: PathBuf::from("/shm"), fs_type: ("tmpfs".to_string(), Some("123".to_string())), super_opts: vec![ 382 | "rw".to_string(), 383 | "size=65536k".to_string(), 384 | ]}), 385 | ]; 386 | 387 | for mi in mountinfo { 388 | let info = parse_mountinfo_for_line(mi.0).unwrap(); 389 | assert_eq!(info, mi.1) 390 | } 391 | } 392 | } 393 | -------------------------------------------------------------------------------- /src/fs/hugetlb.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 Ant Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! This module contains the implementation of the `hugetlb` cgroup subsystem. 8 | //! 9 | //! See the Kernel's documentation for more information about this subsystem, found at: 10 | //! [Documentation/cgroup-v1/hugetlb.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/hugetlb.txt) 11 | use log::warn; 12 | use std::io::Write; 13 | use std::path::PathBuf; 14 | 15 | use crate::fs::error::ErrorKind::*; 16 | use crate::fs::error::*; 17 | use crate::fs::{flat_keyed_to_vec, read_u64_from}; 18 | 19 | use crate::fs::{ 20 | ControllIdentifier, ControllerInternal, Controllers, HugePageResources, Resources, Subsystem, 21 | }; 22 | 23 | /// A controller that allows controlling the `hugetlb` subsystem of a Cgroup. 24 | /// 25 | /// In essence, using this controller it is possible to limit the use of hugepages in the tasks of 26 | /// the control group. 27 | #[derive(Debug, Clone)] 28 | pub struct HugeTlbController { 29 | base: PathBuf, 30 | path: PathBuf, 31 | sizes: Vec, 32 | v2: bool, 33 | } 34 | 35 | impl ControllerInternal for HugeTlbController { 36 | fn control_type(&self) -> Controllers { 37 | Controllers::HugeTlb 38 | } 39 | fn get_path(&self) -> &PathBuf { 40 | &self.path 41 | } 42 | fn get_path_mut(&mut self) -> &mut PathBuf { 43 | &mut self.path 44 | } 45 | fn get_base(&self) -> &PathBuf { 46 | &self.base 47 | } 48 | 49 | fn is_v2(&self) -> bool { 50 | self.v2 51 | } 52 | 53 | fn apply(&self, res: &Resources) -> Result<()> { 54 | // get the resources that apply to this controller 55 | let res: &HugePageResources = &res.hugepages; 56 | 57 | for i in &res.limits { 58 | let _ = self.set_limit_in_bytes(&i.size, i.limit); 59 | if self.limit_in_bytes(&i.size)? != i.limit { 60 | return Err(Error::new(Other)); 61 | } 62 | } 63 | 64 | Ok(()) 65 | } 66 | } 67 | 68 | impl ControllIdentifier for HugeTlbController { 69 | fn controller_type() -> Controllers { 70 | Controllers::HugeTlb 71 | } 72 | } 73 | 74 | impl<'a> From<&'a Subsystem> for &'a HugeTlbController { 75 | fn from(sub: &'a Subsystem) -> &'a HugeTlbController { 76 | unsafe { 77 | match sub { 78 | Subsystem::HugeTlb(c) => c, 79 | _ => { 80 | assert_eq!(1, 0); 81 | let v = std::mem::MaybeUninit::uninit(); 82 | v.assume_init() 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | impl HugeTlbController { 90 | /// Constructs a new `HugeTlbController` with `root` serving as the root of the control group. 91 | pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self { 92 | let sizes = get_hugepage_sizes(); 93 | Self { 94 | base: root, 95 | path: point, 96 | sizes, 97 | v2, 98 | } 99 | } 100 | 101 | /// Whether the system supports `hugetlb_size` hugepages. 102 | pub fn size_supported(&self, hugetlb_size: &str) -> bool { 103 | for s in &self.sizes { 104 | if s == hugetlb_size { 105 | return true; 106 | } 107 | } 108 | false 109 | } 110 | 111 | pub fn get_sizes(&self) -> Vec { 112 | self.sizes.clone() 113 | } 114 | 115 | fn failcnt_v2(&self, hugetlb_size: &str) -> Result { 116 | self.open_path(&format!("hugetlb.{}.events", hugetlb_size), false) 117 | .and_then(flat_keyed_to_vec) 118 | .and_then(|x| { 119 | if x.is_empty() { 120 | return Err(Error::from_string(format!( 121 | "get empty from hugetlb.{}.events", 122 | hugetlb_size 123 | ))); 124 | } 125 | Ok(x[0].1 as u64) 126 | }) 127 | } 128 | 129 | /// Check how many times has the limit of `hugetlb_size` hugepages been hit. 130 | pub fn failcnt(&self, hugetlb_size: &str) -> Result { 131 | if self.v2 { 132 | return self.failcnt_v2(hugetlb_size); 133 | } 134 | self.open_path(&format!("hugetlb.{}.failcnt", hugetlb_size), false) 135 | .and_then(read_u64_from) 136 | } 137 | 138 | /// Get the limit (in bytes) of how much memory can be backed by hugepages of a certain size 139 | /// (`hugetlb_size`). 140 | pub fn limit_in_bytes(&self, hugetlb_size: &str) -> Result { 141 | let mut file_name = format!("hugetlb.{}.limit_in_bytes", hugetlb_size); 142 | if self.v2 { 143 | file_name = format!("hugetlb.{}.max", hugetlb_size); 144 | } 145 | self.open_path(&file_name, false).and_then(read_u64_from) 146 | } 147 | 148 | /// Get the current usage of memory that is backed by hugepages of a certain size 149 | /// (`hugetlb_size`). 150 | pub fn usage_in_bytes(&self, hugetlb_size: &str) -> Result { 151 | let mut file = format!("hugetlb.{}.usage_in_bytes", hugetlb_size); 152 | if self.v2 { 153 | file = format!("hugetlb.{}.current", hugetlb_size); 154 | } 155 | self.open_path(&file, false).and_then(read_u64_from) 156 | } 157 | 158 | /// Get the maximum observed usage of memory that is backed by hugepages of a certain size 159 | /// (`hugetlb_size`). 160 | pub fn max_usage_in_bytes(&self, hugetlb_size: &str) -> Result { 161 | self.open_path( 162 | &format!("hugetlb.{}.max_usage_in_bytes", hugetlb_size), 163 | false, 164 | ) 165 | .and_then(read_u64_from) 166 | } 167 | 168 | /// Set the limit (in bytes) of how much memory can be backed by hugepages of a certain size 169 | /// (`hugetlb_size`). 170 | pub fn set_limit_in_bytes(&self, hugetlb_size: &str, limit: u64) -> Result<()> { 171 | let mut file_name = format!("hugetlb.{}.limit_in_bytes", hugetlb_size); 172 | if self.v2 { 173 | file_name = format!("hugetlb.{}.max", hugetlb_size); 174 | } 175 | self.open_path(&file_name, true).and_then(|mut file| { 176 | file.write_all(limit.to_string().as_ref()).map_err(|e| { 177 | Error::with_cause(WriteFailed(file_name.to_string(), limit.to_string()), e) 178 | }) 179 | }) 180 | } 181 | } 182 | 183 | pub const HUGEPAGESIZE_DIR: &str = "/sys/kernel/mm/hugepages"; 184 | use std::collections::HashMap; 185 | use std::fs; 186 | 187 | fn get_hugepage_sizes() -> Vec { 188 | let dirs = fs::read_dir(HUGEPAGESIZE_DIR); 189 | if dirs.is_err() { 190 | return Vec::new(); 191 | } 192 | 193 | dirs.unwrap() 194 | .filter_map(|e| { 195 | let entry = e.map_err(|e| warn!("readdir error: {:?}", e)).ok()?; 196 | let name = entry.file_name().into_string().unwrap(); 197 | let parts: Vec<&str> = name.split('-').collect(); 198 | if parts.len() != 2 { 199 | return None; 200 | } 201 | let bmap = get_binary_size_map(); 202 | let size = parse_size(parts[1], &bmap) 203 | .map_err(|e| warn!("parse_size error: {:?}", e)) 204 | .ok()?; 205 | let dabbrs = get_decimal_abbrs(); 206 | 207 | Some(custom_size(size as f64, 1024.0, &dabbrs)) 208 | }) 209 | .collect() 210 | } 211 | 212 | pub const KB: u128 = 1000; 213 | pub const MB: u128 = 1000 * KB; 214 | pub const GB: u128 = 1000 * MB; 215 | pub const TB: u128 = 1000 * GB; 216 | pub const PB: u128 = 1000 * TB; 217 | 218 | #[allow(non_upper_case_globals)] 219 | pub const KiB: u128 = 1024; 220 | #[allow(non_upper_case_globals)] 221 | pub const MiB: u128 = 1024 * KiB; 222 | #[allow(non_upper_case_globals)] 223 | pub const GiB: u128 = 1024 * MiB; 224 | #[allow(non_upper_case_globals)] 225 | pub const TiB: u128 = 1024 * GiB; 226 | #[allow(non_upper_case_globals)] 227 | pub const PiB: u128 = 1024 * TiB; 228 | 229 | pub fn get_binary_size_map() -> HashMap { 230 | let mut m = HashMap::new(); 231 | m.insert("k".to_string(), KiB); 232 | m.insert("m".to_string(), MiB); 233 | m.insert("g".to_string(), GiB); 234 | m.insert("t".to_string(), TiB); 235 | m.insert("p".to_string(), PiB); 236 | m 237 | } 238 | 239 | pub fn get_decimal_size_map() -> HashMap { 240 | let mut m = HashMap::new(); 241 | m.insert("k".to_string(), KB); 242 | m.insert("m".to_string(), MB); 243 | m.insert("g".to_string(), GB); 244 | m.insert("t".to_string(), TB); 245 | m.insert("p".to_string(), PB); 246 | m 247 | } 248 | 249 | pub fn get_decimal_abbrs() -> Vec { 250 | let m = vec![ 251 | "B".to_string(), 252 | "KB".to_string(), 253 | "MB".to_string(), 254 | "GB".to_string(), 255 | "TB".to_string(), 256 | "PB".to_string(), 257 | "EB".to_string(), 258 | "ZB".to_string(), 259 | "YB".to_string(), 260 | ]; 261 | m 262 | } 263 | 264 | fn parse_size(s: &str, m: &HashMap) -> Result { 265 | // Remove leading/trailing whitespace. 266 | let s = s.trim(); 267 | 268 | // Remove an optional trailing 'b' or 'B' 269 | let s = if let Some(stripped) = s.strip_suffix('b').or_else(|| s.strip_suffix('B')) { 270 | stripped 271 | } else { 272 | s 273 | }; 274 | 275 | // Ensure that the string is not empty after stripping. 276 | if s.is_empty() { 277 | return Err(Error::new(InvalidBytesSize)); 278 | } 279 | 280 | // The last character should be the multiplier letter. 281 | let last_char = s.chars().last().unwrap(); 282 | if !"kKmMgGtTpP".contains(last_char) { 283 | return Err(Error::new(InvalidBytesSize)); 284 | } 285 | 286 | // The numeric part is everything before the multiplier letter. 287 | let num_part = &s[..s.len() - last_char.len_utf8()]; 288 | if num_part.trim().is_empty() { 289 | return Err(Error::new(InvalidBytesSize)); 290 | } 291 | 292 | // Parse the numeric part into a u128. 293 | let number: u128 = num_part 294 | .trim() 295 | .parse() 296 | .map_err(|_| Error::new(InvalidBytesSize))?; 297 | 298 | // Look up the multiplier in the provided HashMap. 299 | let multiplier_key = last_char.to_string(); 300 | let multiplier = m 301 | .get(&multiplier_key) 302 | .ok_or_else(|| Error::new(InvalidBytesSize))?; 303 | 304 | Ok(number * multiplier) 305 | } 306 | 307 | fn custom_size(mut size: f64, base: f64, m: &[String]) -> String { 308 | let mut i = 0; 309 | while size >= base && i < m.len() - 1 { 310 | size /= base; 311 | i += 1; 312 | } 313 | 314 | format!("{}{}", size, m[i].as_str()) 315 | } 316 | 317 | #[cfg(test)] 318 | mod tests { 319 | use super::*; 320 | 321 | #[test] 322 | fn test_binary_size_valid() { 323 | let m = get_binary_size_map(); 324 | // Valid inputs must include a multiplier letter. 325 | assert_eq!(parse_size("1k", &m).unwrap(), KiB); 326 | assert_eq!(parse_size("2m", &m).unwrap(), 2 * MiB); 327 | assert_eq!(parse_size("3g", &m).unwrap(), 3 * GiB); 328 | assert_eq!(parse_size("4t", &m).unwrap(), 4 * TiB); 329 | assert_eq!(parse_size("5p", &m).unwrap(), 5 * PiB); 330 | } 331 | 332 | #[test] 333 | fn test_decimal_size_valid() { 334 | let m = get_decimal_size_map(); 335 | assert_eq!(parse_size("1k", &m).unwrap(), KB); 336 | assert_eq!(parse_size("2m", &m).unwrap(), 2 * MB); 337 | assert_eq!(parse_size("3g", &m).unwrap(), 3 * GB); 338 | assert_eq!(parse_size("4t", &m).unwrap(), 4 * TB); 339 | assert_eq!(parse_size("5p", &m).unwrap(), 5 * PB); 340 | } 341 | 342 | #[test] 343 | fn test_trailing_b_suffix() { 344 | let m = get_binary_size_map(); 345 | // Trailing 'b' or 'B' should be accepted. 346 | assert_eq!(parse_size("1kb", &m).unwrap(), KiB); 347 | assert_eq!(parse_size("2mB", &m).unwrap(), 2 * MiB); 348 | } 349 | 350 | #[test] 351 | fn test_invalid_inputs() { 352 | let m = get_binary_size_map(); 353 | // Missing multiplier letter results in error. 354 | assert!(parse_size("1", &m).is_err()); 355 | // Invalid multiplier letter. 356 | assert!(parse_size("10x", &m).is_err()); 357 | // Non-numeric input. 358 | assert!(parse_size("abc", &m).is_err()); 359 | // Only multiplier letter with no number. 360 | assert!(parse_size("k", &m).is_err()); 361 | // Number with an invalid trailing character. 362 | assert!(parse_size("123z", &m).is_err()); 363 | } 364 | 365 | #[test] 366 | fn test_uppercase_multiplier_fails() { 367 | let m = get_binary_size_map(); 368 | // Although the regex matches uppercase letters, the provided map only contains lowercase keys. 369 | // Therefore, "1K" does not match any key and should produce an error. 370 | assert!(parse_size("1K", &m).is_err()); 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /src/fs/net_cls.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | //! This module contains the implementation of the `net_cls` cgroup subsystem. 7 | //! 8 | //! See the Kernel's documentation for more information about this subsystem, found at: 9 | //! [Documentation/cgroup-v1/net_cls.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/net_cls.txt) 10 | use std::io::Write; 11 | use std::path::PathBuf; 12 | 13 | use crate::fs::error::ErrorKind::*; 14 | use crate::fs::error::*; 15 | 16 | use crate::fs::read_u64_from; 17 | use crate::fs::{ 18 | ControllIdentifier, ControllerInternal, Controllers, NetworkResources, Resources, Subsystem, 19 | }; 20 | 21 | /// A controller that allows controlling the `net_cls` subsystem of a Cgroup. 22 | /// 23 | /// In esssence, using the `net_cls` controller, one can attach a custom class to the network 24 | /// packets emitted by the control group's tasks. This can then later be used in iptables to have 25 | /// custom firewall rules, QoS, etc. 26 | #[derive(Debug, Clone)] 27 | pub struct NetClsController { 28 | base: PathBuf, 29 | path: PathBuf, 30 | } 31 | 32 | impl ControllerInternal for NetClsController { 33 | fn control_type(&self) -> Controllers { 34 | Controllers::NetCls 35 | } 36 | fn get_path(&self) -> &PathBuf { 37 | &self.path 38 | } 39 | fn get_path_mut(&mut self) -> &mut PathBuf { 40 | &mut self.path 41 | } 42 | fn get_base(&self) -> &PathBuf { 43 | &self.base 44 | } 45 | 46 | fn apply(&self, res: &Resources) -> Result<()> { 47 | // get the resources that apply to this controller 48 | let res: &NetworkResources = &res.network; 49 | 50 | update_and_test!(self, set_class, res.class_id, get_class); 51 | 52 | Ok(()) 53 | } 54 | } 55 | 56 | impl ControllIdentifier for NetClsController { 57 | fn controller_type() -> Controllers { 58 | Controllers::NetCls 59 | } 60 | } 61 | 62 | impl<'a> From<&'a Subsystem> for &'a NetClsController { 63 | fn from(sub: &'a Subsystem) -> &'a NetClsController { 64 | unsafe { 65 | match sub { 66 | Subsystem::NetCls(c) => c, 67 | _ => { 68 | assert_eq!(1, 0); 69 | let v = std::mem::MaybeUninit::uninit(); 70 | v.assume_init() 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | impl NetClsController { 78 | /// Constructs a new `NetClsController` with `root` serving as the root of the control group. 79 | pub fn new(point: PathBuf, root: PathBuf) -> Self { 80 | Self { 81 | base: root, 82 | path: point, 83 | } 84 | } 85 | 86 | /// Set the network class id of the outgoing packets of the control group's tasks. 87 | pub fn set_class(&self, class: u64) -> Result<()> { 88 | self.open_path("net_cls.classid", true) 89 | .and_then(|mut file| { 90 | let s = format!("{:#08X}", class); 91 | file.write_all(s.as_ref()).map_err(|e| { 92 | Error::with_cause(WriteFailed("net_cls.classid".to_string(), s), e) 93 | }) 94 | }) 95 | } 96 | 97 | /// Get the network class id of the outgoing packets of the control group's tasks. 98 | pub fn get_class(&self) -> Result { 99 | self.open_path("net_cls.classid", false) 100 | .and_then(read_u64_from) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/fs/net_prio.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | //! This module contains the implementation of the `net_prio` cgroup subsystem. 7 | //! 8 | //! See the Kernel's documentation for more information about this subsystem, found at: 9 | //! [Documentation/cgroup-v1/net_prio.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/net_prio.txt) 10 | use std::collections::HashMap; 11 | use std::io::{BufRead, BufReader, Write}; 12 | use std::path::PathBuf; 13 | 14 | use crate::fs::error::ErrorKind::*; 15 | use crate::fs::error::*; 16 | 17 | use crate::fs::read_u64_from; 18 | use crate::fs::{ 19 | ControllIdentifier, ControllerInternal, Controllers, NetworkResources, Resources, Subsystem, 20 | }; 21 | 22 | /// A controller that allows controlling the `net_prio` subsystem of a Cgroup. 23 | /// 24 | /// In essence, using `net_prio` one can set the priority of the packets emitted from the control 25 | /// group's tasks. This can then be used to have QoS restrictions on certain control groups and 26 | /// thus, prioritizing certain tasks. 27 | #[derive(Debug, Clone)] 28 | pub struct NetPrioController { 29 | base: PathBuf, 30 | path: PathBuf, 31 | } 32 | 33 | impl ControllerInternal for NetPrioController { 34 | fn control_type(&self) -> Controllers { 35 | Controllers::NetPrio 36 | } 37 | fn get_path(&self) -> &PathBuf { 38 | &self.path 39 | } 40 | fn get_path_mut(&mut self) -> &mut PathBuf { 41 | &mut self.path 42 | } 43 | fn get_base(&self) -> &PathBuf { 44 | &self.base 45 | } 46 | 47 | fn apply(&self, res: &Resources) -> Result<()> { 48 | // get the resources that apply to this controller 49 | let res: &NetworkResources = &res.network; 50 | 51 | for i in &res.priorities { 52 | let _ = self.set_if_prio(&i.name, i.priority); 53 | } 54 | 55 | Ok(()) 56 | } 57 | } 58 | 59 | impl ControllIdentifier for NetPrioController { 60 | fn controller_type() -> Controllers { 61 | Controllers::NetPrio 62 | } 63 | } 64 | 65 | impl<'a> From<&'a Subsystem> for &'a NetPrioController { 66 | fn from(sub: &'a Subsystem) -> &'a NetPrioController { 67 | unsafe { 68 | match sub { 69 | Subsystem::NetPrio(c) => c, 70 | _ => { 71 | assert_eq!(1, 0); 72 | let v = std::mem::MaybeUninit::uninit(); 73 | v.assume_init() 74 | } 75 | } 76 | } 77 | } 78 | } 79 | 80 | impl NetPrioController { 81 | /// Constructs a new `NetPrioController` with `root` serving as the root of the control group. 82 | pub fn new(point: PathBuf, root: PathBuf) -> Self { 83 | Self { 84 | base: root, 85 | path: point, 86 | } 87 | } 88 | 89 | /// Retrieves the current priority of the emitted packets. 90 | pub fn prio_idx(&self) -> u64 { 91 | self.open_path("net_prio.prioidx", false) 92 | .and_then(read_u64_from) 93 | .unwrap_or(0) 94 | } 95 | 96 | /// A map of priorities for each network interface. 97 | pub fn ifpriomap(&self) -> Result> { 98 | self.open_path("net_prio.ifpriomap", false) 99 | .and_then(|file| { 100 | let bf = BufReader::new(file); 101 | bf.lines() 102 | .map(|line| { 103 | let line = line.map_err(|_| Error::new(ParseError))?; 104 | let mut parts = line.split_whitespace(); 105 | 106 | let ifname = parts.next().ok_or(Error::new(ParseError))?; 107 | let ifprio_str = parts.next().ok_or(Error::new(ParseError))?; 108 | 109 | let ifprio = ifprio_str 110 | .trim() 111 | .parse() 112 | .map_err(|e| Error::with_cause(ParseError, e))?; 113 | 114 | Ok((ifname.to_string(), ifprio)) 115 | }) 116 | .collect::>>() 117 | }) 118 | } 119 | 120 | /// Set the priority of the network traffic on `eif` to be `prio`. 121 | pub fn set_if_prio(&self, eif: &str, prio: u64) -> Result<()> { 122 | self.open_path("net_prio.ifpriomap", true) 123 | .and_then(|mut file| { 124 | file.write_all(format!("{} {}", eif, prio).as_ref()) 125 | .map_err(|e| { 126 | Error::with_cause( 127 | WriteFailed( 128 | "net_prio.ifpriomap".to_string(), 129 | format!("{} {}", eif, prio), 130 | ), 131 | e, 132 | ) 133 | }) 134 | }) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/fs/perf_event.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | //! This module contains the implementation of the `perf_event` cgroup subsystem. 7 | //! 8 | //! See the Kernel's documentation for more information about this subsystem, found at: 9 | //! [tools/perf/Documentation/perf-record.txt](https://raw.githubusercontent.com/torvalds/linux/master/tools/perf/Documentation/perf-record.txt) 10 | use std::path::PathBuf; 11 | 12 | use crate::fs::error::*; 13 | 14 | use crate::fs::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; 15 | 16 | /// A controller that allows controlling the `perf_event` subsystem of a Cgroup. 17 | /// 18 | /// In essence, when processes belong to the same `perf_event` controller, they can be monitored 19 | /// together using the `perf` performance monitoring and reporting tool. 20 | #[derive(Debug, Clone)] 21 | pub struct PerfEventController { 22 | base: PathBuf, 23 | path: PathBuf, 24 | } 25 | 26 | impl ControllerInternal for PerfEventController { 27 | fn control_type(&self) -> Controllers { 28 | Controllers::PerfEvent 29 | } 30 | fn get_path(&self) -> &PathBuf { 31 | &self.path 32 | } 33 | fn get_path_mut(&mut self) -> &mut PathBuf { 34 | &mut self.path 35 | } 36 | fn get_base(&self) -> &PathBuf { 37 | &self.base 38 | } 39 | 40 | fn apply(&self, _res: &Resources) -> Result<()> { 41 | Ok(()) 42 | } 43 | } 44 | 45 | impl ControllIdentifier for PerfEventController { 46 | fn controller_type() -> Controllers { 47 | Controllers::PerfEvent 48 | } 49 | } 50 | 51 | impl<'a> From<&'a Subsystem> for &'a PerfEventController { 52 | fn from(sub: &'a Subsystem) -> &'a PerfEventController { 53 | unsafe { 54 | match sub { 55 | Subsystem::PerfEvent(c) => c, 56 | _ => { 57 | assert_eq!(1, 0); 58 | let v = std::mem::MaybeUninit::uninit(); 59 | v.assume_init() 60 | } 61 | } 62 | } 63 | } 64 | } 65 | 66 | impl PerfEventController { 67 | /// Constructs a new `PerfEventController` with `root` serving as the root of the control group. 68 | pub fn new(point: PathBuf, root: PathBuf) -> Self { 69 | Self { 70 | base: root, 71 | path: point, 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/fs/pid.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 Ant Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! This module contains the implementation of the `pids` cgroup subsystem. 8 | //! 9 | //! See the Kernel's documentation for more information about this subsystem, found at: 10 | //! [Documentation/cgroups-v1/pids.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/pids.txt) 11 | use std::io::{Read, Write}; 12 | use std::path::PathBuf; 13 | 14 | use crate::fs::error::ErrorKind::*; 15 | use crate::fs::error::*; 16 | 17 | use crate::fs::read_u64_from; 18 | use crate::fs::{ 19 | parse_max_value, ControllIdentifier, ControllerInternal, Controllers, MaxValue, PidResources, 20 | Resources, Subsystem, 21 | }; 22 | 23 | /// A controller that allows controlling the `pids` subsystem of a Cgroup. 24 | #[derive(Debug, Clone)] 25 | pub struct PidController { 26 | base: PathBuf, 27 | path: PathBuf, 28 | v2: bool, 29 | } 30 | 31 | impl ControllerInternal for PidController { 32 | fn control_type(&self) -> Controllers { 33 | Controllers::Pids 34 | } 35 | fn get_path(&self) -> &PathBuf { 36 | &self.path 37 | } 38 | fn get_path_mut(&mut self) -> &mut PathBuf { 39 | &mut self.path 40 | } 41 | fn get_base(&self) -> &PathBuf { 42 | &self.base 43 | } 44 | 45 | fn is_v2(&self) -> bool { 46 | self.v2 47 | } 48 | 49 | fn apply(&self, res: &Resources) -> Result<()> { 50 | // get the resources that apply to this controller 51 | let pidres: &PidResources = &res.pid; 52 | 53 | // apply pid_max 54 | update_and_test!( 55 | self, 56 | set_pid_max, 57 | pidres.maximum_number_of_processes, 58 | get_pid_max 59 | ); 60 | 61 | Ok(()) 62 | } 63 | } 64 | 65 | // impl<'a> ControllIdentifier for &'a PidController { 66 | // fn controller_type() -> Controllers { 67 | // Controllers::Pids 68 | // } 69 | // } 70 | 71 | impl ControllIdentifier for PidController { 72 | fn controller_type() -> Controllers { 73 | Controllers::Pids 74 | } 75 | } 76 | 77 | impl<'a> From<&'a Subsystem> for &'a PidController { 78 | fn from(sub: &'a Subsystem) -> &'a PidController { 79 | unsafe { 80 | match sub { 81 | Subsystem::Pid(c) => c, 82 | _ => { 83 | assert_eq!(1, 0); 84 | let v = std::mem::MaybeUninit::uninit(); 85 | v.assume_init() 86 | } 87 | } 88 | } 89 | } 90 | } 91 | 92 | impl PidController { 93 | /// Constructors a new `PidController` instance, with `root` serving as the controller's root 94 | /// directory. 95 | pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self { 96 | Self { 97 | base: root, 98 | path: point, 99 | v2, 100 | } 101 | } 102 | 103 | /// The number of times `fork` failed because the limit was hit. 104 | pub fn get_pid_events(&self) -> Result { 105 | self.open_path("pids.events", false).and_then(|mut file| { 106 | let mut string = String::new(); 107 | match file.read_to_string(&mut string) { 108 | Ok(_) => match string.split_whitespace().nth(1) { 109 | Some(elem) => match elem.parse() { 110 | Ok(val) => Ok(val), 111 | Err(e) => Err(Error::with_cause(ParseError, e)), 112 | }, 113 | None => Err(Error::new(ParseError)), 114 | }, 115 | Err(e) => Err(Error::with_cause(ReadFailed("pids.events".to_string()), e)), 116 | } 117 | }) 118 | } 119 | 120 | /// The number of processes currently. 121 | pub fn get_pid_current(&self) -> Result { 122 | self.open_path("pids.current", false) 123 | .and_then(read_u64_from) 124 | } 125 | 126 | /// The maximum number of processes that can exist at one time in the control group. 127 | pub fn get_pid_max(&self) -> Result { 128 | self.open_path("pids.max", false).and_then(|mut file| { 129 | let mut string = String::new(); 130 | let res = file.read_to_string(&mut string); 131 | match res { 132 | Ok(_) => parse_max_value(&string), 133 | Err(e) => Err(Error::with_cause(ReadFailed("pids.max".to_string()), e)), 134 | } 135 | }) 136 | } 137 | 138 | /// Set the maximum number of processes that can exist in this control group. 139 | /// 140 | /// Note that if `get_pid_current()` returns a higher number than what you 141 | /// are about to set (`max_pid`), then no processess will be killed. Additonally, attaching 142 | /// extra processes to a control group disregards the limit. 143 | pub fn set_pid_max(&self, max_pid: MaxValue) -> Result<()> { 144 | self.open_path("pids.max", true).and_then(|mut file| { 145 | let string_to_write = max_pid.to_string(); 146 | match file.write_all(string_to_write.as_ref()) { 147 | Ok(_) => Ok(()), 148 | Err(e) => Err(Error::with_cause( 149 | WriteFailed("pids.max".to_string(), format!("{:?}", max_pid)), 150 | e, 151 | )), 152 | } 153 | }) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/fs/rdma.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | //! This module contains the implementation of the `rdma` cgroup subsystem. 7 | //! 8 | //! See the Kernel's documentation for more information about this subsystem, found at: 9 | //! [Documentation/cgroup-v1/rdma.txt](https://www.kernel.org/doc/Documentation/cgroup-v1/rdma.txt) 10 | use std::io::Write; 11 | use std::path::PathBuf; 12 | 13 | use crate::fs::error::ErrorKind::*; 14 | use crate::fs::error::*; 15 | 16 | use crate::fs::read_string_from; 17 | use crate::fs::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; 18 | 19 | /// A controller that allows controlling the `rdma` subsystem of a Cgroup. 20 | /// 21 | /// In essence, using this controller one can limit the RDMA/IB specific resources that the tasks 22 | /// in the control group can use. 23 | #[derive(Debug, Clone)] 24 | pub struct RdmaController { 25 | base: PathBuf, 26 | path: PathBuf, 27 | } 28 | 29 | impl ControllerInternal for RdmaController { 30 | fn control_type(&self) -> Controllers { 31 | Controllers::Rdma 32 | } 33 | fn get_path(&self) -> &PathBuf { 34 | &self.path 35 | } 36 | fn get_path_mut(&mut self) -> &mut PathBuf { 37 | &mut self.path 38 | } 39 | fn get_base(&self) -> &PathBuf { 40 | &self.base 41 | } 42 | 43 | fn apply(&self, _res: &Resources) -> Result<()> { 44 | Ok(()) 45 | } 46 | } 47 | 48 | impl ControllIdentifier for RdmaController { 49 | fn controller_type() -> Controllers { 50 | Controllers::Rdma 51 | } 52 | } 53 | 54 | impl<'a> From<&'a Subsystem> for &'a RdmaController { 55 | fn from(sub: &'a Subsystem) -> &'a RdmaController { 56 | unsafe { 57 | match sub { 58 | Subsystem::Rdma(c) => c, 59 | _ => { 60 | assert_eq!(1, 0); 61 | let v = std::mem::MaybeUninit::uninit(); 62 | v.assume_init() 63 | } 64 | } 65 | } 66 | } 67 | } 68 | 69 | impl RdmaController { 70 | /// Constructs a new `RdmaController` with `root` serving as the root of the control group. 71 | pub fn new(point: PathBuf, root: PathBuf) -> Self { 72 | Self { 73 | base: root, 74 | path: point, 75 | } 76 | } 77 | 78 | /// Returns the current usage of RDMA/IB specific resources. 79 | pub fn current(&self) -> Result { 80 | self.open_path("rdma.current", false) 81 | .and_then(read_string_from) 82 | } 83 | 84 | /// Returns the max usage of RDMA/IB specific resources. 85 | pub fn max(&self) -> Result { 86 | self.open_path("rdma.max", false).and_then(read_string_from) 87 | } 88 | 89 | /// Set a maximum usage for each RDMA/IB resource. 90 | pub fn set_max(&self, max: &str) -> Result<()> { 91 | self.open_path("rdma.max", true).and_then(|mut file| { 92 | file.write_all(max.as_ref()).map_err(|e| { 93 | Error::with_cause(WriteFailed("rdma.max".to_string(), max.to_string()), e) 94 | }) 95 | }) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/fs/systemd.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Ant Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | //! This module contains the implementation of the `systemd` cgroup subsystem. 7 | //! 8 | use std::path::PathBuf; 9 | 10 | use crate::fs::error::*; 11 | 12 | use crate::fs::{ControllIdentifier, ControllerInternal, Controllers, Resources, Subsystem}; 13 | 14 | /// A controller that allows controlling the `systemd` subsystem of a Cgroup. 15 | /// 16 | #[derive(Debug, Clone)] 17 | pub struct SystemdController { 18 | base: PathBuf, 19 | path: PathBuf, 20 | _v2: bool, 21 | } 22 | 23 | impl ControllerInternal for SystemdController { 24 | fn control_type(&self) -> Controllers { 25 | Controllers::Systemd 26 | } 27 | fn get_path(&self) -> &PathBuf { 28 | &self.path 29 | } 30 | fn get_path_mut(&mut self) -> &mut PathBuf { 31 | &mut self.path 32 | } 33 | fn get_base(&self) -> &PathBuf { 34 | &self.base 35 | } 36 | 37 | fn apply(&self, _res: &Resources) -> Result<()> { 38 | Ok(()) 39 | } 40 | } 41 | 42 | impl ControllIdentifier for SystemdController { 43 | fn controller_type() -> Controllers { 44 | Controllers::Systemd 45 | } 46 | } 47 | 48 | impl<'a> From<&'a Subsystem> for &'a SystemdController { 49 | fn from(sub: &'a Subsystem) -> &'a SystemdController { 50 | unsafe { 51 | match sub { 52 | Subsystem::Systemd(c) => c, 53 | _ => { 54 | assert_eq!(1, 0); 55 | let v = std::mem::MaybeUninit::uninit(); 56 | v.assume_init() 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | impl SystemdController { 64 | /// Constructs a new `SystemdController` with `root` serving as the root of the control group. 65 | pub fn new(point: PathBuf, root: PathBuf, v2: bool) -> Self { 66 | Self { 67 | base: root, 68 | path: point, 69 | _v2: v2, 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020-2025 Ant Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | pub mod fs; 8 | #[cfg(feature = "oci")] 9 | pub mod manager; 10 | #[cfg(feature = "oci")] 11 | pub use manager::{FsManager, Manager, SystemdManager}; 12 | pub mod stats; 13 | pub use stats::CgroupStats; 14 | pub mod systemd; 15 | 16 | /// The maximum value for CPU shares in cgroups v1 17 | pub const CPU_SHARES_V1_MAX: u64 = 262144; 18 | /// The maximum value for CPU weight in cgroups v2 19 | pub const CPU_WEIGHT_V2_MAX: u64 = 10000; 20 | 21 | /// The current state of the control group 22 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 23 | pub enum FreezerState { 24 | /// The processes in the control group are _not_ frozen. 25 | Thawed, 26 | /// The processes in the control group are in the processes of being frozen. 27 | Freezing, 28 | /// The processes in the control group are frozen. 29 | Frozen, 30 | } 31 | 32 | /// A structure representing a `pid`. Currently implementations exist for `u64` and 33 | /// `std::process::Child`. 34 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] 35 | pub struct CgroupPid { 36 | /// The process identifier 37 | pub pid: u64, 38 | } 39 | 40 | impl From for CgroupPid { 41 | fn from(u: u64) -> CgroupPid { 42 | CgroupPid { pid: u } 43 | } 44 | } 45 | 46 | impl From<&std::process::Child> for CgroupPid { 47 | fn from(u: &std::process::Child) -> CgroupPid { 48 | CgroupPid { pid: u.id() as u64 } 49 | } 50 | } 51 | 52 | #[cfg(test)] 53 | pub mod tests { 54 | use std::fs; 55 | use std::process::{Child, Command, Stdio}; 56 | 57 | /// Start a mock subprocess that will sleep forever 58 | pub fn spawn_sleep_inf() -> Child { 59 | let child = Command::new("sleep") 60 | .arg("infinity") 61 | .spawn() 62 | .expect("Failed to start mock subprocess"); 63 | child 64 | } 65 | 66 | pub fn spawn_yes() -> Child { 67 | let devnull = fs::File::create("/dev/null").expect("cannot open /dev/null"); 68 | let child = Command::new("yes") 69 | .stdout(Stdio::from(devnull)) 70 | .spawn() 71 | .expect("Failed to start mock subprocess"); 72 | child 73 | } 74 | 75 | pub fn systemd_version() -> Option { 76 | let output = Command::new("systemd").arg("--version").output().ok()?; // Return None if command execution fails 77 | if !output.status.success() { 78 | return None; 79 | } 80 | Some(String::from_utf8_lossy(&output.stdout).to_string()) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/manager/conv.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Ant Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | use crate::manager::error::{Error, Result}; 7 | use crate::{CPU_SHARES_V1_MAX, CPU_WEIGHT_V2_MAX}; 8 | 9 | // Converts CPU shares, used by cgroup v1, to CPU weight, used by cgroup 10 | // v2. 11 | // 12 | // Cgroup v1 CPU shares has a range of [2^1...2^18], i.e. [2...262144], 13 | // and the default value is 1024. 14 | // 15 | // Cgroup v2 CPU weight has a range of [10^0...10^4], i.e. [1...10000], 16 | // and the default value is 100. 17 | pub(crate) fn cpu_shares_to_cgroup_v2(shares: u64) -> u64 { 18 | if shares == 0 { 19 | return 0; 20 | } 21 | if shares <= 2 { 22 | return 1; 23 | } 24 | if shares >= CPU_SHARES_V1_MAX { 25 | return CPU_WEIGHT_V2_MAX; 26 | } 27 | 28 | (((shares - 2) * 9999) / 262142) + 1 29 | } 30 | 31 | // ConvertMemorySwapToCgroupV2Value converts MemorySwap value from OCI spec 32 | // for use by cgroup v2 drivers. A conversion is needed since 33 | // Resources.MemorySwap is defined as memory+swap combined, while in cgroup 34 | // v2 swap is a separate value. 35 | pub(crate) fn memory_swap_to_cgroup_v2(memswap_limit: i64, mem_limit: i64) -> Result { 36 | // For compatibility with cgroup1 controller, set swap to unlimited in 37 | // case the memory is set to unlimited, and swap is not explicitly set, 38 | // treating the request as "set both memory and swap to unlimited". 39 | if mem_limit == -1 && memswap_limit == 0 { 40 | return Ok(-1); 41 | } 42 | 43 | // -1 is "max", 0 is "unset", so treat as is 44 | if memswap_limit == -1 || memswap_limit == 0 { 45 | return Ok(memswap_limit); 46 | } 47 | 48 | // Unlimited memory, so treat swap as is. 49 | if mem_limit == -1 { 50 | return Ok(memswap_limit); 51 | } 52 | 53 | // Unset or unknown memory, can't calculate swap. 54 | if mem_limit == 0 { 55 | return Err(Error::InvalidLinuxResource); 56 | } 57 | 58 | // Does not make sense to subtract a negative value. 59 | if mem_limit < 0 { 60 | return Err(Error::InvalidLinuxResource); 61 | } 62 | 63 | // Sanity check. 64 | if memswap_limit < mem_limit { 65 | return Err(Error::InvalidLinuxResource); 66 | } 67 | 68 | Ok(memswap_limit - mem_limit) 69 | } 70 | 71 | #[cfg(test)] 72 | mod tests { 73 | use crate::manager::conv::*; 74 | 75 | #[test] 76 | fn test_cpu_shares_to_cgroup_v2() { 77 | assert_eq!(cpu_shares_to_cgroup_v2(0), 0); 78 | assert_eq!(cpu_shares_to_cgroup_v2(1), 1); 79 | assert_eq!(cpu_shares_to_cgroup_v2(2), 1); 80 | assert_eq!(cpu_shares_to_cgroup_v2(100), 4); 81 | assert_eq!( 82 | cpu_shares_to_cgroup_v2(CPU_SHARES_V1_MAX), 83 | CPU_WEIGHT_V2_MAX 84 | ); 85 | assert_eq!( 86 | cpu_shares_to_cgroup_v2(CPU_SHARES_V1_MAX - 1), 87 | CPU_WEIGHT_V2_MAX - 1 88 | ); 89 | assert_eq!(cpu_shares_to_cgroup_v2(u64::MAX), CPU_WEIGHT_V2_MAX); 90 | } 91 | 92 | #[test] 93 | fn test_memory_swap_to_cgroup_v2() { 94 | // memory no limit and swap is 0, treat it as no limit 95 | assert_eq!(memory_swap_to_cgroup_v2(0, -1).unwrap(), -1); 96 | // -1 is "max", 0 is "unset", so treat as is 97 | assert_eq!(memory_swap_to_cgroup_v2(-1, 0).unwrap(), -1); 98 | assert_eq!(memory_swap_to_cgroup_v2(0, 0).unwrap(), 0); 99 | 100 | // Now swap cannot be 0 or -1 101 | 102 | // Unlimited memory, so treat swap as is. 103 | assert_eq!(memory_swap_to_cgroup_v2(100, -1).unwrap(), 100); 104 | // Unset or unknown memory, can't calculate swap. 105 | assert!(memory_swap_to_cgroup_v2(100, 0).is_err()); 106 | // Does not make sense to subtract a negative value. 107 | assert!(memory_swap_to_cgroup_v2(100, -2).is_err()); 108 | // Swap + mem < mem 109 | assert!(memory_swap_to_cgroup_v2(50, 100).is_err()); 110 | // Real swap 111 | assert_eq!(memory_swap_to_cgroup_v2(200, 100).unwrap(), 100); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/manager/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Ant Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | use crate::fs::error::Error as CgroupfsError; 7 | use crate::systemd::dbus::error::Error as SystemdDbusError; 8 | use crate::systemd::error::Error as SystemdCgroupError; 9 | 10 | pub type Result = std::result::Result; 11 | 12 | #[derive(thiserror::Error, Debug)] 13 | pub enum Error { 14 | #[error("invalid argument")] 15 | InvalidArgument, 16 | 17 | #[error("invalid linux resource")] 18 | InvalidLinuxResource, 19 | 20 | #[error("cgroupfs error: {0}")] 21 | Cgroupfs(#[from] CgroupfsError), 22 | 23 | #[error("systemd cgroup error: {0}")] 24 | SystemdCgroup(#[from] SystemdCgroupError), 25 | 26 | #[error("systemd dbus error: {0}")] 27 | SystemdDbus(#[from] SystemdDbusError), 28 | } 29 | -------------------------------------------------------------------------------- /src/manager/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Ant Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | mod error; 7 | use std::collections::HashMap; 8 | 9 | pub use error::{Error, Result}; 10 | mod fs; 11 | pub use fs::FsManager; 12 | mod systemd; 13 | pub use systemd::SystemdManager; 14 | mod conv; 15 | 16 | use oci_spec::runtime::LinuxResources; 17 | 18 | use crate::systemd::SLICE_SUFFIX; 19 | use crate::{CgroupPid, CgroupStats, FreezerState}; 20 | 21 | /// Check if the cgroups path is a systemd cgroup. 22 | pub fn is_systemd_cgroup(cgroups_path: &str) -> bool { 23 | let parts: Vec<&str> = cgroups_path.split(':').collect(); 24 | parts.len() == 3 && parts[0].ends_with(SLICE_SUFFIX) 25 | } 26 | 27 | /// Manage cgroups designed for OCI containers. 28 | pub trait Manager: Send + Sync { 29 | /// Add a process specified by its tgid. 30 | fn add_proc(&mut self, tgid: CgroupPid) -> Result<()>; 31 | 32 | /// Add a thread specified by its pid. 33 | fn add_thread(&mut self, pid: CgroupPid) -> Result<()>; 34 | 35 | /// Get the list of pids joint to the cgroups. 36 | fn pids(&self) -> Result>; 37 | 38 | /// Set the freezer cgroup to the specified state. 39 | fn freeze(&self, state: FreezerState) -> Result<()>; 40 | 41 | /// Remove the cgroups. 42 | fn destroy(&mut self) -> Result<()>; 43 | 44 | /// Set the resources to the cgroups. 45 | fn set(&mut self, resources: &LinuxResources) -> Result<()>; 46 | 47 | /// Get the cgroup path. 48 | /// 49 | /// # Arguments 50 | /// 51 | /// - `subsystem`: cgroup subsystem, for cgroup v1 the value should not 52 | /// be empty, while for cgroup v2 the only valid value is `None`. 53 | fn cgroup_path(&self, subsystem: Option<&str>) -> Result; 54 | 55 | /// Enable CPUs, topdown from root in cgroup hierarchy, this would be 56 | /// useful for CPU hotplug in the guest. 57 | /// 58 | /// The caller should update cgroup resources manually, in particular 59 | /// cpuset, after this, in order to use the new CPUs (or avoid using 60 | /// offline CPUs). 61 | /// 62 | /// # Arguments 63 | /// 64 | /// - `cpus`: online CPUs in the same format with `cat 65 | /// /sys/devices/system/cpu/online`, e.g. "0-3,6-7". 66 | fn enable_cpus_topdown(&self, cpus: &str) -> Result<()>; 67 | 68 | /// Get cgroup stats. 69 | fn stats(&self) -> CgroupStats; 70 | 71 | /// Get the mappings of subsystems to their relative path. The full 72 | /// path would be something like "{mountpoint}/{relative_path}". The 73 | /// mappings of mountpoints see "mounts()". 74 | fn paths(&self) -> &HashMap; 75 | 76 | /// Get the mappings of subsystems to their mountpoints. The full 77 | /// path would be something like "{mountpoint}/{relative_path}". The 78 | /// mappings of relative paths see "paths()". 79 | fn mounts(&self) -> &HashMap; 80 | 81 | /// Indicate whether the cgroup manager is using systemd. 82 | fn systemd(&self) -> bool; 83 | 84 | /// Indicate whether the cgroup manager is using cgroup v2. 85 | fn v2(&self) -> bool; 86 | } 87 | 88 | #[cfg(test)] 89 | mod tests { 90 | pub const MEMORY_512M: i64 = 512 * 1024 * 1024; // 512 MiB 91 | pub const MEMORY_1G: i64 = 1024 * 1024 * 1024; // 1 GiB 92 | pub const MEMORY_2G: i64 = 2 * 1024 * 1024 * 1024; // 2 GiB 93 | 94 | #[macro_export] 95 | macro_rules! skip_if_cgroups_v1 { 96 | () => { 97 | if !$crate::fs::hierarchies::is_cgroup2_unified_mode() { 98 | eprintln!("Skipping test in cgroups v1 mode"); 99 | return; 100 | } 101 | }; 102 | } 103 | 104 | #[macro_export] 105 | macro_rules! skip_if_cgroups_v2 { 106 | () => { 107 | if $crate::fs::hierarchies::is_cgroup2_unified_mode() { 108 | eprintln!("Skipping test in cgroups v2 mode"); 109 | return; 110 | } 111 | }; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/stats.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2025 Ant Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | use std::collections::HashMap; 8 | 9 | #[derive(Debug, Default)] 10 | pub struct CgroupStats { 11 | pub cpu: CpuCgroupStats, 12 | pub memory: MemoryCgroupStats, 13 | pub pids: PidsCgroupStats, 14 | pub blkio: BlkioCgroupStats, 15 | pub hugetlb: HugeTlbCgroupStats, 16 | } 17 | 18 | #[derive(Debug, Default)] 19 | pub struct CpuCgroupStats { 20 | pub cpu_acct: Option, 21 | pub cpu_throttling: Option, 22 | } 23 | 24 | #[derive(Debug, Default)] 25 | pub struct CpuAcctStats { 26 | /// Usage in userspace, read from `cpuacct.stat` from the line starting 27 | /// with `user`. Set 0 if no data. 28 | pub user_usage: u64, 29 | /// Usage in kernelspace, read from `cpuacct.stat` from the line 30 | /// starting with `system`. Set 0 if no data. 31 | pub system_usage: u64, 32 | /// Total usage, read from `cpuacct.usage`. Set 0 if no data. 33 | pub total_usage: u64, 34 | /// Per-CPU usage, read from `cpuacct.usage_percpu`. 35 | pub usage_percpu: Vec, 36 | } 37 | 38 | #[derive(Debug, Default)] 39 | pub struct CpuThrottlingStats { 40 | /// Periods, read from `cpu.stat` from the line starting with 41 | /// `nr_periods`. Set 0 if no data. 42 | pub periods: u64, 43 | /// Throttled periods, read from `cpu.stat` from the line starting with 44 | /// `nr_throttled`. Set 0 if no data. 45 | pub throttled_periods: u64, 46 | /// Throttled time, read from `cpu.stat` from the line starting with 47 | /// `throttled_time`. Set 0 if no data. 48 | pub throttled_time: u64, 49 | } 50 | 51 | #[derive(Debug, Default)] 52 | pub struct MemoryCgroupStats { 53 | pub memory: Option, 54 | pub memory_swap: Option, 55 | pub kernel_memory: Option, 56 | 57 | /// Use hierarchy, read from `memory.use_hierarchy` in cgroups v1. Only 58 | /// available in cgroups v1. 59 | pub use_hierarchy: bool, 60 | 61 | // The following data is read from `memory.stat`, see also 62 | // `crate::fs::memory::MemoryStat::stat`. 63 | pub cache: u64, 64 | pub rss: u64, 65 | pub rss_huge: u64, 66 | pub shmem: u64, 67 | pub mapped_file: u64, 68 | pub dirty: u64, 69 | pub writeback: u64, 70 | pub swap: u64, 71 | pub pgpgin: u64, 72 | pub pgpgout: u64, 73 | pub pgfault: u64, 74 | pub pgmajfault: u64, 75 | pub inactive_anon: u64, 76 | pub active_anon: u64, 77 | pub inactive_file: u64, 78 | pub active_file: u64, 79 | pub unevictable: u64, 80 | pub hierarchical_memory_limit: i64, 81 | pub hierarchical_memsw_limit: i64, 82 | pub total_cache: u64, 83 | pub total_rss: u64, 84 | pub total_rss_huge: u64, 85 | pub total_shmem: u64, 86 | pub total_mapped_file: u64, 87 | pub total_dirty: u64, 88 | pub total_writeback: u64, 89 | pub total_swap: u64, 90 | pub total_pgpgin: u64, 91 | pub total_pgpgout: u64, 92 | pub total_pgfault: u64, 93 | pub total_pgmajfault: u64, 94 | pub total_inactive_anon: u64, 95 | pub total_active_anon: u64, 96 | pub total_inactive_file: u64, 97 | pub total_active_file: u64, 98 | pub total_unevictable: u64, 99 | } 100 | 101 | #[derive(Debug, Default)] 102 | pub struct MemoryStats { 103 | /// Memory [swap] usage, read from `memory[.memsw].usage_in_bytes` in 104 | /// cgroups v1 and `memory[.swap].current` in cgroups v2. 105 | pub usage: u64, 106 | /// Maximum memory [swap] usage observed by cgroups, read from 107 | /// `memory[.memsw].max_usage_in_bytes` in cgroups v1 and 108 | /// `memory[.swap].peak` in cgroups v2. 109 | pub max_usage: u64, 110 | /// Memory [swap] limit, read from `memory[.memsw].limit_in_bytes` in 111 | /// cgroups v1 and `memory[.swap].max` in cgroups v2. 112 | pub limit: i64, 113 | /// Failure count, read from `memory[.memsw].failcnt`. Only available in 114 | /// cgroups v1. 115 | pub fail_cnt: u64, 116 | } 117 | 118 | #[derive(Debug, Default)] 119 | pub struct PidsCgroupStats { 120 | /// Current number of processes in the cgroup, read from `pids.current`. 121 | pub current: u64, 122 | /// Maximum number of processes in the cgroup, read from `pids.limit`. 123 | pub limit: i64, 124 | } 125 | 126 | #[derive(Debug, Default)] 127 | pub struct BlkioCgroupStats { 128 | pub io_service_bytes_recursive: Vec, 129 | pub io_serviced_recursive: Vec, 130 | pub io_queued_recursive: Vec, 131 | pub io_service_time_recursive: Vec, 132 | pub io_wait_time_recursive: Vec, 133 | pub io_merged_recursive: Vec, 134 | pub io_time_recursive: Vec, 135 | pub sectors_recursive: Vec, 136 | } 137 | 138 | #[derive(Debug, Default)] 139 | pub struct BlkioStat { 140 | pub major: u64, 141 | pub minor: u64, 142 | pub op: String, 143 | pub value: u64, 144 | } 145 | 146 | /// A structure representing the statistics of the `hugetlb` subsystem of a 147 | /// Cgroup. The key is the huge page size, and the value is the statistics 148 | /// for that size. 149 | pub type HugeTlbCgroupStats = HashMap; 150 | 151 | #[derive(Debug, Default)] 152 | pub struct HugeTlbStat { 153 | pub usage: u64, 154 | pub max_usage: u64, 155 | pub fail_cnt: u64, 156 | } 157 | -------------------------------------------------------------------------------- /src/systemd/consts.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Ant Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | /// Who enum: all 7 | pub const WHO_ENUM_ALL: &str = "all"; 8 | 9 | /// Unit mode: replace 10 | pub const UNIT_MODE_REPLACE: &str = "replace"; 11 | 12 | /// No such unit error 13 | pub const NO_SUCH_UNIT: &str = "org.freedesktop.systemd1.NoSuchUnit"; 14 | 15 | /// Default description for transient units. 16 | pub const DEFAULT_DESCRIPTION: &str = "cgroups-rs transient unit"; 17 | 18 | /// Turn on CPU usage accounting for this unit. 19 | pub const CPU_ACCOUNTING: &str = "CPUAccounting"; 20 | /// This setting controls the memory controller in the unified hierarchy. 21 | /// Added in version 208. 22 | pub const MEMORY_ACCOUNTING: &str = "MemoryAccounting"; 23 | /// This setting controls the pids controller in the unified hierarchy. 24 | pub const TASKS_ACCOUNTING: &str = "TasksAccounting"; 25 | /// This setting controls the io controller in the unified hierarchy. 26 | /// Added in version 230. 27 | pub const IO_ACCOUNTING: &str = "IOAccounting"; 28 | /// This setting controls the block IO controller in the legacy hierarchy. 29 | /// Deprecated in version 252. 30 | pub const BLOCK_IO_ACCOUNTING: &str = "BlockIOAccounting"; 31 | /// Description of the unit. 32 | pub const DESCRIPTION: &str = "Description"; 33 | /// PIDs 34 | pub const PIDS: &str = "PIDs"; 35 | /// Default dependencies for this unit. 36 | pub const DEFAULT_DEPENDENCIES: &str = "DefaultDependencies"; 37 | /// Wants, expressing a weak dependency on other units. 38 | pub const WANTS: &str = "Wants"; 39 | /// Slice, used to assign a unit to a specific slice. 40 | pub const SLICE: &str = "Slice"; 41 | /// Turns on delegation of further resource control partitioning to 42 | /// processes of the unit. 43 | pub const DELEGATE: &str = "Delegate"; 44 | /// Timeout for stopping the unit in microseconds. 45 | pub const TIMEOUT_STOP_USEC: &str = "TimeoutStopUSec"; 46 | 47 | /// CPU shares in the legacy hierarchy. 48 | pub const CPU_SHARES: &str = "CPUShares"; 49 | /// CPU shares in the unified hierarchy. 50 | pub const CPU_WEIGHT: &str = "CPUWeight"; 51 | /// CPU quota period us. 52 | pub const CPU_QUOTA_PERIOD_US: &str = "CPUQuotaPeriodUSec"; 53 | /// CPU quota us 54 | pub const CPU_QUOTA_PER_SEC_US: &str = "CPUQuotaPerSecUSec"; 55 | /// Allowed CPUs 56 | pub const ALLOWED_CPUS: &str = "AllowedCPUs"; 57 | /// Allowed memory nodes 58 | pub const ALLOWED_MEMORY_NODES: &str = "AllowedMemoryNodes"; 59 | /// Memory limit in the legacy hierarchy. 60 | pub const MEMORY_LIMIT: &str = "MemoryLimit"; 61 | /// Memory limit in the unified hierarchy. 62 | pub const MEMORY_MAX: &str = "MemoryMax"; 63 | /// Memory low 64 | pub const MEMORY_LOW: &str = "MemoryLow"; 65 | /// Memory swap max 66 | pub const MEMORY_SWAP_MAX: &str = "MemorySwapMax"; 67 | /// Tasks max 68 | pub const TASKS_MAX: &str = "TasksMax"; 69 | -------------------------------------------------------------------------------- /src/systemd/cpu.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Ant Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | use crate::systemd::error::Result; 7 | use crate::systemd::{CPU_QUOTA_PERIOD_US, CPU_QUOTA_PER_SEC_US, CPU_SHARES, CPU_WEIGHT}; 8 | 9 | /// Returns the property for CPU shares. 10 | /// 11 | /// Please note that if the shares is obtained from OCI runtime spec, it 12 | /// MUST be converted, see [1] and `convert_shares_to_v2()`. 13 | /// 14 | /// 1: https://github.com/containers/crun/blob/main/crun.1.md#cgroup-v2 15 | pub fn shares(shares: u64, v2: bool) -> Result<(&'static str, u64)> { 16 | let id = if v2 { CPU_WEIGHT } else { CPU_SHARES }; 17 | 18 | Ok((id, shares)) 19 | } 20 | 21 | /// Returns the property for CPU period. 22 | pub fn period(period: u64) -> Result<(&'static str, u64)> { 23 | Ok((CPU_QUOTA_PERIOD_US, period)) 24 | } 25 | 26 | /// Return the property for CPU quota. 27 | pub fn quota(quota: u64) -> Result<(&'static str, u64)> { 28 | Ok((CPU_QUOTA_PER_SEC_US, quota)) 29 | } 30 | -------------------------------------------------------------------------------- /src/systemd/cpuset.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Ant Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | use bit_vec::BitVec; 7 | 8 | use crate::systemd::error::{Error, Result}; 9 | use crate::systemd::{ALLOWED_CPUS, ALLOWED_MEMORY_NODES}; 10 | 11 | const BYTE_IN_BITS: usize = 8; 12 | 13 | /// Returns the property for cpuset CPUs. 14 | pub fn cpus(cpus: &str) -> Result<(&'static str, Vec)> { 15 | let mask = convert_list_to_mask(cpus)?; 16 | Ok((ALLOWED_CPUS, mask)) 17 | } 18 | 19 | /// Returns the property for cpuset memory nodes. 20 | pub fn mems(mems: &str) -> Result<(&'static str, Vec)> { 21 | let mask = convert_list_to_mask(mems)?; 22 | Ok((ALLOWED_MEMORY_NODES, mask)) 23 | } 24 | 25 | /// Convert cpuset cpus/mems from the string in comma-separated list format 26 | /// to bitmask restored in `Vec`, see [1]. 27 | /// 28 | /// 1: https://man7.org/linux/man-pages/man7/cpuset.7.html 29 | /// 30 | /// # Arguments 31 | /// 32 | /// * `list` - A string slice that holds the list of CPUs in the format 33 | /// "0-3,5,7". 34 | fn convert_list_to_mask(list: &str) -> Result> { 35 | let mut bit_vec = BitVec::from_elem(8, false); 36 | 37 | let local_idx = 38 | |index: usize| -> usize { index / BYTE_IN_BITS * BYTE_IN_BITS + 7 - index % BYTE_IN_BITS }; 39 | 40 | for part1 in list.split(',') { 41 | let range: Vec<&str> = part1.split('-').collect(); 42 | match range.len() { 43 | // x- 44 | 1 => { 45 | let left: usize = range[0].parse().map_err(|_| Error::InvalidArgument)?; 46 | 47 | while left >= bit_vec.len() { 48 | bit_vec.grow(BYTE_IN_BITS, false); 49 | } 50 | bit_vec.set(local_idx(left), true); 51 | } 52 | // x-y 53 | 2 => { 54 | let left: usize = range[0].parse().map_err(|_| Error::InvalidArgument)?; 55 | let right: usize = range[1].parse().map_err(|_| Error::InvalidArgument)?; 56 | 57 | while right >= bit_vec.len() { 58 | bit_vec.grow(BYTE_IN_BITS, false); 59 | } 60 | 61 | for index in left..=right { 62 | bit_vec.set(local_idx(index), true); 63 | } 64 | } 65 | _ => { 66 | return Err(Error::InvalidArgument); 67 | } 68 | } 69 | } 70 | 71 | let mut mask = bit_vec.to_bytes(); 72 | mask.reverse(); 73 | 74 | Ok(mask) 75 | } 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | use crate::systemd::cpuset::convert_list_to_mask; 80 | 81 | #[test] 82 | fn test_convert_list_to_mask() { 83 | let mask = convert_list_to_mask("2-4").unwrap(); 84 | assert_eq!(vec![0b00011100_u8], mask); 85 | 86 | let mask = convert_list_to_mask("1,7").unwrap(); 87 | assert_eq!(vec![0b10000010_u8], mask); 88 | 89 | let mask = convert_list_to_mask("0-4,9").unwrap(); 90 | assert_eq!(vec![0b00000010_u8, 0b00011111_u8], mask); 91 | 92 | assert!(convert_list_to_mask("1-3-4").is_err()); 93 | 94 | assert!(convert_list_to_mask("1-3,,").is_err()); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/systemd/dbus/README.md: -------------------------------------------------------------------------------- 1 | # Systemd Dbus 2 | 3 | How to generate `xxx_proxy.rs` files 4 | 5 | ```shell 6 | # install zbus-xmlgen if not 7 | $ cargo install zbus-xmlgen 8 | # generate interface in XML format 9 | $ busctl introspect --xml-interface \ 10 | org.freedesktop.systemd1 \ 11 | /org/freedesktop/systemd1 \ 12 | org.freedesktop.systemd1.Manager > /tmp/systemd1-manager.xml 13 | # generate Rust code from XML 14 | $ zbus-xmlgen file /tmp/systemd1-manager.xml \ 15 | --output src/systemd/dbus/systemd_manager_proxy.rs 16 | $ rm -rf /tmp/systemd1-manager.xml 17 | ``` 18 | -------------------------------------------------------------------------------- /src/systemd/dbus/client.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2023 Kata Contributors 2 | // Copyright (c) 2025 Ant Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | use zbus::zvariant::Value; 8 | use zbus::{Error as ZbusError, Result as ZbusResult}; 9 | 10 | use crate::systemd::dbus::error::{Error, Result}; 11 | use crate::systemd::dbus::proxy::systemd_manager_proxy; 12 | use crate::systemd::{Property, NO_SUCH_UNIT, PIDS, UNIT_MODE_REPLACE}; 13 | use crate::CgroupPid; 14 | 15 | pub struct SystemdClient<'a> { 16 | /// The name of the systemd unit (slice or scope) 17 | unit: String, 18 | props: Vec>, 19 | } 20 | 21 | impl<'a> SystemdClient<'a> { 22 | pub fn new(unit: &str, props: Vec>) -> Result { 23 | Ok(Self { 24 | unit: unit.to_string(), 25 | props, 26 | }) 27 | } 28 | } 29 | 30 | impl SystemdClient<'_> { 31 | /// Set the pid to the PIDs property of the unit. 32 | /// 33 | /// Append a process ID to the PIDs property of the unit. If not 34 | /// exists, one property will be created. 35 | pub fn set_pid_prop(&mut self, pid: CgroupPid) -> Result<()> { 36 | if self.exists() { 37 | return Ok(()); 38 | } 39 | 40 | for prop in self.props.iter_mut() { 41 | if prop.0 == PIDS { 42 | // If PIDS is already set, we append the new pid to the existing list. 43 | if let Value::Array(arr) = &mut prop.1 { 44 | arr.append(pid.pid.into()) 45 | .map_err(|_| Error::InvalidProperties)?; 46 | return Ok(()); 47 | } 48 | // Invalid type of PIDs 49 | return Err(Error::InvalidProperties); 50 | } 51 | } 52 | // If PIDS is not set, we create a new property. 53 | self.props 54 | .push((PIDS, Value::Array(vec![pid.pid as u32].into()))); 55 | Ok(()) 56 | } 57 | 58 | /// Start a slice or a scope unit controlled and supervised by systemd. 59 | /// 60 | /// For more information, see: 61 | /// https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html 62 | /// https://www.freedesktop.org/software/systemd/man/latest/systemd.slice.html 63 | /// https://www.freedesktop.org/software/systemd/man/latest/systemd.scope.html 64 | pub fn start(&self) -> Result<()> { 65 | // PIDs property must be present 66 | if !self.props.iter().any(|(k, _)| k == &PIDS) { 67 | return Err(Error::InvalidProperties); 68 | } 69 | 70 | let sys_proxy = systemd_manager_proxy()?; 71 | 72 | let props_borrowed: Vec<(&str, &zbus::zvariant::Value)> = 73 | self.props.iter().map(|(k, v)| (*k, v)).collect(); 74 | let props_borrowed: Vec<&(&str, &Value)> = props_borrowed.iter().collect(); 75 | 76 | sys_proxy.start_transient_unit(&self.unit, UNIT_MODE_REPLACE, &props_borrowed, &[])?; 77 | 78 | Ok(()) 79 | } 80 | 81 | /// Stop the current transient unit, the processes will be killed on 82 | /// unit stop, see [1]. 83 | /// 84 | /// 1. https://www.freedesktop.org/software/systemd/man/latest/systemd.kill.html#KillMode= 85 | pub fn stop(&self) -> Result<()> { 86 | let sys_proxy = systemd_manager_proxy()?; 87 | 88 | let ret = sys_proxy.stop_unit(&self.unit, UNIT_MODE_REPLACE); 89 | ignore_no_such_unit(ret)?; 90 | 91 | // If we stop the unit and it still exists, it may be in a failed 92 | // state, so we will try to reset it. 93 | if self.exists() { 94 | let ret = sys_proxy.reset_failed_unit(&self.unit); 95 | ignore_no_such_unit(ret)?; 96 | } 97 | 98 | Ok(()) 99 | } 100 | 101 | /// Set properties for the unit through dbus `SetUnitProperties`. 102 | pub fn set_properties(&mut self, properties: &[Property<'static>]) -> Result<()> { 103 | for prop in properties { 104 | let new = prop.1.try_clone().map_err(|_| Error::InvalidProperties)?; 105 | // Try to update the value first, if fails, append it. 106 | if let Some(existing) = self.props.iter_mut().find(|p| p.0 == prop.0) { 107 | existing.1 = new; 108 | } else { 109 | self.props.push((prop.0, new)); 110 | } 111 | } 112 | 113 | // The unit must exist before setting properties. 114 | if !self.exists() { 115 | return Ok(()); 116 | } 117 | 118 | let sys_proxy = systemd_manager_proxy()?; 119 | 120 | let props_borrowed: Vec<(&str, &Value)> = properties.iter().map(|(k, v)| (*k, v)).collect(); 121 | let props_borrowed: Vec<&(&str, &Value)> = props_borrowed.iter().collect(); 122 | 123 | sys_proxy.set_unit_properties(&self.unit, true, &props_borrowed)?; 124 | 125 | Ok(()) 126 | } 127 | 128 | /// Freeze the unit through dbus `FreezeUnit`. 129 | pub fn freeze(&self) -> Result<()> { 130 | let sys_proxy = systemd_manager_proxy()?; 131 | 132 | sys_proxy.freeze_unit(&self.unit)?; 133 | 134 | Ok(()) 135 | } 136 | 137 | /// Thaw the frozen unit through dbus `ThawUnit`. 138 | pub fn thaw(&self) -> Result<()> { 139 | let sys_proxy = systemd_manager_proxy()?; 140 | 141 | sys_proxy.thaw_unit(&self.unit)?; 142 | 143 | Ok(()) 144 | } 145 | 146 | /// Check if the unit exists. 147 | pub fn exists(&self) -> bool { 148 | let sys_proxy = match systemd_manager_proxy() { 149 | Ok(proxy) => proxy, 150 | _ => return false, 151 | }; 152 | 153 | sys_proxy 154 | .get_unit(&self.unit) 155 | .map(|_| true) 156 | .unwrap_or_default() 157 | } 158 | 159 | /// Add a process (tgid) to the unit through dbus 160 | /// `AttachProcessesToUnit`. 161 | pub fn add_process(&self, pid: CgroupPid, subcgroup: &str) -> Result<()> { 162 | let sys_proxy = systemd_manager_proxy()?; 163 | 164 | sys_proxy.attach_processes_to_unit(&self.unit, subcgroup, &[pid.pid as u32])?; 165 | 166 | Ok(()) 167 | } 168 | } 169 | 170 | fn ignore_no_such_unit(result: ZbusResult) -> ZbusResult { 171 | if let Err(ZbusError::MethodError(err_name, _, _)) = &result { 172 | if err_name.as_str() == NO_SUCH_UNIT { 173 | return Ok(true); 174 | } 175 | } 176 | result.map(|_| false) 177 | } 178 | 179 | #[cfg(test)] 180 | pub mod tests { 181 | //! Unit tests for the SystemdClient 182 | //! 183 | //! Not sure why the tests are going to fail if we run them in 184 | //! parallel. Everything goes smoothly in serial. 185 | //! 186 | //! $ cargo test --package cgroups-rs --lib \ 187 | //! -- systemd::dbus::client::tests \ 188 | //! --show-output --test-threads=1 189 | 190 | use std::fs; 191 | use std::path::Path; 192 | use std::process::Command; 193 | use std::thread::sleep; 194 | use std::time::Duration; 195 | 196 | use rand::distributions::Alphanumeric; 197 | use rand::Rng; 198 | 199 | use crate::fs::hierarchies; 200 | use crate::systemd::dbus::client::*; 201 | use crate::systemd::props::PropertiesBuilder; 202 | use crate::systemd::utils::expand_slice; 203 | use crate::systemd::{DEFAULT_DESCRIPTION, DESCRIPTION, PIDS}; 204 | use crate::tests::{spawn_sleep_inf, spawn_yes}; 205 | 206 | const TEST_SLICE: &str = "cgroupsrs-test.slice"; 207 | 208 | fn test_unit() -> String { 209 | let rand_string: String = rand::thread_rng() 210 | .sample_iter(&Alphanumeric) 211 | .take(5) 212 | .map(char::from) 213 | .collect(); 214 | format!("cri-pod{}.scope", rand_string) 215 | } 216 | 217 | #[macro_export] 218 | macro_rules! skip_if_no_systemd { 219 | () => { 220 | if $crate::tests::systemd_version().is_none() { 221 | eprintln!("Test skipped, no systemd?"); 222 | return; 223 | } 224 | }; 225 | } 226 | 227 | fn systemd_show(unit: &str) -> String { 228 | let output = Command::new("systemctl") 229 | .arg("show") 230 | .arg(unit) 231 | .output() 232 | .expect("Failed to execute systemctl show command"); 233 | String::from_utf8_lossy(&output.stdout).to_string() 234 | } 235 | 236 | fn start_default_cgroup(pid: CgroupPid, unit: &str) -> SystemdClient { 237 | let mut props = PropertiesBuilder::default_cgroup(TEST_SLICE, unit).build(); 238 | props.push((PIDS, Value::Array(vec![pid.pid as u32].into()))); 239 | let cgroup = SystemdClient::new(unit, props).unwrap(); 240 | // Stop the unit if it exists. 241 | cgroup.stop().unwrap(); 242 | 243 | // Write the current process to the cgroup. 244 | cgroup.start().unwrap(); 245 | cgroup.add_process(pid, "/").unwrap(); 246 | cgroup 247 | } 248 | 249 | fn stop_cgroup(cgroup: &SystemdClient) { 250 | cgroup.stop().unwrap(); 251 | } 252 | 253 | #[test] 254 | fn test_start() { 255 | skip_if_no_systemd!(); 256 | 257 | let v2 = hierarchies::is_cgroup2_unified_mode(); 258 | let unit = test_unit(); 259 | let mut child = spawn_sleep_inf(); 260 | let cgroup = start_default_cgroup(CgroupPid::from(child.id() as u64), &unit); 261 | 262 | let base = expand_slice(TEST_SLICE).unwrap(); 263 | 264 | // Check if the cgroup exists in the filesystem 265 | let full_base = if v2 { 266 | format!("/sys/fs/cgroup/{}", base) 267 | } else { 268 | format!("/sys/fs/cgroup/memory/{}", base) 269 | }; 270 | assert!( 271 | Path::new(&full_base).exists(), 272 | "Cgroup base path does not exist: {}", 273 | full_base 274 | ); 275 | 276 | // PIDs 277 | let cgroup_procs_path = format!("{}/{}/cgroup.procs", full_base, &unit); 278 | for i in 0..5 { 279 | let content = fs::read_to_string(&cgroup_procs_path); 280 | if let Ok(content) = &content { 281 | if content.contains(&child.id().to_string()) { 282 | break; 283 | } 284 | } 285 | // Retry attempts exhausted, resulting in failure 286 | if i == 4 { 287 | let content = content.as_ref().unwrap(); 288 | assert!( 289 | content.contains(&child.id().to_string()), 290 | "Cgroup procs does not contain the child process ID" 291 | ); 292 | } 293 | // Wait 500ms before next retrying 294 | sleep(Duration::from_millis(500)); 295 | } 296 | 297 | // Check the unit from "systemctl show " 298 | let output = systemd_show(&cgroup.unit); 299 | 300 | // Slice 301 | assert!( 302 | output 303 | .lines() 304 | .any(|line| line == format!("Slice={}", TEST_SLICE)), 305 | "Slice not found" 306 | ); 307 | // Delegate 308 | assert!( 309 | output.lines().any(|line| line == "Delegate=yes"), 310 | "Delegate not set" 311 | ); 312 | // DelegateControllers 313 | // controllers: cpu cpuacct cpuset io blkio memory devices pids 314 | let controllers = output 315 | .lines() 316 | .find(|line| line.starts_with("DelegateControllers=")) 317 | .map(|line| line.trim_start_matches("DelegateControllers=")) 318 | .unwrap(); 319 | let controllers = controllers.split(' ').collect::>(); 320 | assert!( 321 | controllers.contains(&"cpu"), 322 | "DelegateControllers cpu not set" 323 | ); 324 | assert!( 325 | controllers.contains(&"cpuset"), 326 | "DelegateControllers cpuset not set" 327 | ); 328 | if v2 { 329 | assert!( 330 | controllers.contains(&"io"), 331 | "DelegateControllers io not set" 332 | ); 333 | } else { 334 | assert!( 335 | controllers.contains(&"blkio"), 336 | "DelegateControllers blkio not set" 337 | ); 338 | } 339 | assert!( 340 | controllers.contains(&"memory"), 341 | "DelegateControllers memory not set" 342 | ); 343 | assert!( 344 | controllers.contains(&"pids"), 345 | "DelegateControllers pids not set" 346 | ); 347 | 348 | // CPUAccounting 349 | assert!( 350 | output.lines().any(|line| line == "CPUAccounting=yes"), 351 | "CPUAccounting not set" 352 | ); 353 | // IOAccounting for v2, and BlockIOAccounting for v1 354 | if v2 { 355 | assert!( 356 | output.lines().any(|line| line == "IOAccounting=yes"), 357 | "IOAccounting not set" 358 | ); 359 | } else { 360 | assert!( 361 | output.lines().any(|line| line == "BlockIOAccounting=yes"), 362 | "BlockIOAccounting not set" 363 | ); 364 | } 365 | // MemoryAccounting 366 | assert!( 367 | output.lines().any(|line| line == "MemoryAccounting=yes"), 368 | "MemoryAccounting not set" 369 | ); 370 | // TasksAccounting 371 | assert!( 372 | output.lines().any(|line| line == "TasksAccounting=yes"), 373 | "TasksAccounting not set" 374 | ); 375 | // ActiveState 376 | assert!( 377 | output.lines().any(|line| line == "ActiveState=active"), 378 | "Unit is not active" 379 | ); 380 | 381 | stop_cgroup(&cgroup); 382 | child.wait().unwrap(); 383 | } 384 | 385 | #[test] 386 | fn test_stop() { 387 | skip_if_no_systemd!(); 388 | 389 | let unit = test_unit(); 390 | let mut child = spawn_sleep_inf(); 391 | let cgroup = start_default_cgroup(CgroupPid::from(child.id() as u64), &unit); 392 | 393 | // Check ActiveState: expected to be "active" 394 | let output = systemd_show(&cgroup.unit); 395 | assert!( 396 | output.lines().any(|line| line == "ActiveState=active"), 397 | "Unit is not active" 398 | ); 399 | 400 | stop_cgroup(&cgroup); 401 | 402 | // Check ActiveState: expected to be "inactive" 403 | let output = systemd_show(&cgroup.unit); 404 | assert!( 405 | output.lines().any(|line| line == "ActiveState=inactive"), 406 | "Unit is not inactive" 407 | ); 408 | 409 | child.wait().unwrap(); 410 | } 411 | 412 | #[test] 413 | fn test_set_properties() { 414 | skip_if_no_systemd!(); 415 | 416 | let unit = test_unit(); 417 | let mut child = spawn_sleep_inf(); 418 | let mut cgroup = start_default_cgroup(CgroupPid::from(child.id() as u64), &unit); 419 | 420 | let output = systemd_show(&cgroup.unit); 421 | assert!( 422 | output.lines().any(|line| line 423 | == format!( 424 | "Description={} {}:{}", 425 | DEFAULT_DESCRIPTION, TEST_SLICE, unit 426 | )), 427 | "Initial description not set correctly" 428 | ); 429 | 430 | let properties = [( 431 | DESCRIPTION, 432 | Value::Str("kata-container1 description".into()), 433 | )]; 434 | cgroup.set_properties(&properties).unwrap(); 435 | assert!(cgroup.props.iter().any(|(k, v)| { 436 | k == &DESCRIPTION && v == &Value::Str("kata-container1 description".into()) 437 | })); 438 | 439 | let output = systemd_show(&cgroup.unit); 440 | assert!( 441 | output 442 | .lines() 443 | .any(|line| line == "Description=kata-container1 description"), 444 | "Updated description not set correctly" 445 | ); 446 | 447 | stop_cgroup(&cgroup); 448 | child.wait().unwrap(); 449 | } 450 | 451 | #[test] 452 | fn test_freeze_and_thaw() { 453 | skip_if_no_systemd!(); 454 | 455 | let unit = test_unit(); 456 | let mut child = spawn_yes(); 457 | let cgroup = start_default_cgroup(CgroupPid::from(child.id() as u64), &unit); 458 | 459 | // Freeze the unit 460 | cgroup.freeze().unwrap(); 461 | 462 | let pid = child.id() as u64; 463 | 464 | let stat_path = format!("/proc/{}/stat", pid); 465 | let content = fs::read_to_string(&stat_path).unwrap(); 466 | // The process state is the third field, e.g.: 467 | // 1234 (bash) S 1233 ... 468 | // ^ 469 | let mut content_iter = content.split_whitespace(); 470 | assert_eq!( 471 | content_iter.nth(2).unwrap(), 472 | "S", 473 | "Process should be in 'S' (sleeping) state after freezing" 474 | ); 475 | 476 | // Thaw the unit 477 | cgroup.thaw().unwrap(); 478 | 479 | // No more S now 480 | let content = fs::read_to_string(&stat_path).unwrap(); 481 | let mut content_iter = content.split_whitespace(); 482 | assert_ne!( 483 | content_iter.nth(2).unwrap(), 484 | "S", 485 | "Process should not be in 'S' (sleeping) state after thawing" 486 | ); 487 | 488 | stop_cgroup(&cgroup); 489 | child.wait().unwrap(); 490 | } 491 | 492 | #[test] 493 | fn test_exists() { 494 | skip_if_no_systemd!(); 495 | 496 | let unit = test_unit(); 497 | let mut child = spawn_sleep_inf(); 498 | let cgroup = start_default_cgroup(CgroupPid::from(child.id() as u64), &unit); 499 | 500 | assert!(cgroup.exists(), "Cgroup should exist after starting"); 501 | 502 | stop_cgroup(&cgroup); 503 | child.wait().unwrap(); 504 | } 505 | 506 | #[test] 507 | fn test_add_process() { 508 | skip_if_no_systemd!(); 509 | 510 | let unit = test_unit(); 511 | let mut child = spawn_sleep_inf(); 512 | let cgroup = start_default_cgroup(CgroupPid::from(child.id() as u64), &unit); 513 | 514 | let mut child1 = spawn_sleep_inf(); 515 | let pid1 = CgroupPid::from(child1.id() as u64); 516 | cgroup.add_process(pid1, "/").unwrap(); 517 | 518 | let cgroup_procs_path = format!( 519 | "/sys/fs/cgroup/{}/{}/cgroup.procs", 520 | expand_slice(TEST_SLICE).unwrap(), 521 | unit 522 | ); 523 | for i in 0..5 { 524 | let content = fs::read_to_string(&cgroup_procs_path); 525 | if let Ok(content) = content { 526 | assert!( 527 | content.contains(&child1.id().to_string()), 528 | "Cgroup procs does not contain the child1 process ID" 529 | ); 530 | break; 531 | } 532 | // Retry attempts exhausted, resulting in failure 533 | if i == 4 { 534 | content.unwrap(); 535 | } 536 | // Wait 500ms before next retrying 537 | sleep(Duration::from_millis(500)); 538 | } 539 | 540 | stop_cgroup(&cgroup); 541 | child.wait().unwrap(); 542 | child1.wait().unwrap(); 543 | } 544 | } 545 | -------------------------------------------------------------------------------- /src/systemd/dbus/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Ant Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | 6 | pub type Result = std::result::Result; 7 | 8 | #[derive(Debug, thiserror::Error)] 9 | pub enum Error { 10 | #[error("invalid properties")] 11 | InvalidProperties, 12 | 13 | #[error("dbus error: {0}")] 14 | Dbus(#[from] zbus::Error), 15 | } 16 | -------------------------------------------------------------------------------- /src/systemd/dbus/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020-2025 Ant Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! Systemd D-Bus interface for managing cgroups and units. 8 | //! 9 | //! References: 10 | //! https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.systemd1.html 11 | //! https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html 12 | //! https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html 13 | 14 | mod client; 15 | pub mod error; 16 | mod systemd_manager_proxy; 17 | pub use client::SystemdClient; 18 | mod proxy; 19 | -------------------------------------------------------------------------------- /src/systemd/dbus/proxy.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Ant Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | use zbus::blocking::Connection; 7 | use zbus::Result; 8 | 9 | use crate::systemd::dbus::systemd_manager_proxy::ManagerProxyBlocking as SystemManager; 10 | 11 | pub(crate) fn systemd_manager_proxy<'a>() -> Result> { 12 | let connection = Connection::system()?; 13 | let proxy = SystemManager::new(&connection)?; 14 | 15 | Ok(proxy) 16 | } 17 | -------------------------------------------------------------------------------- /src/systemd/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Ant Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | pub type Result = std::result::Result; 7 | 8 | #[derive(Debug, thiserror::Error)] 9 | pub enum Error { 10 | #[error("invalid argument")] 11 | InvalidArgument, 12 | 13 | #[error("resource not supported by cgroups v1")] 14 | CgroupsV1NotSupported, 15 | } 16 | -------------------------------------------------------------------------------- /src/systemd/memory.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Ant Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | use crate::systemd::error::{Error, Result}; 7 | use crate::systemd::{MEMORY_LIMIT, MEMORY_LOW, MEMORY_MAX, MEMORY_SWAP_MAX}; 8 | 9 | /// Returns the property for memory limit. 10 | pub fn limit(limit: i64, v2: bool) -> Result<(&'static str, u64)> { 11 | let id = if v2 { MEMORY_MAX } else { MEMORY_LIMIT }; 12 | 13 | Ok((id, limit as u64)) 14 | } 15 | 16 | /// Returns the property for memory limit. 17 | pub fn low(low: i64, v2: bool) -> Result<(&'static str, u64)> { 18 | if !v2 { 19 | return Err(Error::CgroupsV1NotSupported); 20 | } 21 | 22 | Ok((MEMORY_LOW, low as u64)) 23 | } 24 | 25 | /// Returns the property for memory swap. 26 | pub fn swap(swap: i64, v2: bool) -> Result<(&'static str, u64)> { 27 | if !v2 { 28 | return Err(Error::CgroupsV1NotSupported); 29 | } 30 | 31 | Ok((MEMORY_SWAP_MAX, swap as u64)) 32 | } 33 | -------------------------------------------------------------------------------- /src/systemd/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Ant Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | pub mod cpu; 7 | pub mod cpuset; 8 | pub mod dbus; 9 | pub use dbus::SystemdClient; 10 | mod consts; 11 | pub use consts::*; 12 | pub mod error; 13 | pub mod memory; 14 | pub mod pids; 15 | pub mod props; 16 | pub use props::Property; 17 | pub mod utils; 18 | 19 | pub const DEFAULT_SLICE: &str = "system.slice"; 20 | 21 | pub const SLICE_SUFFIX: &str = ".slice"; 22 | pub const SCOPE_SUFFIX: &str = ".scope"; 23 | -------------------------------------------------------------------------------- /src/systemd/pids.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Ant Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | use crate::systemd::error::Result; 7 | use crate::systemd::TASKS_MAX; 8 | 9 | pub fn max(max: i64) -> Result<(&'static str, u64)> { 10 | Ok((TASKS_MAX, max as u64)) 11 | } 12 | -------------------------------------------------------------------------------- /src/systemd/props.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Ant Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | use zbus::zvariant::Value as ZbusValue; 7 | 8 | use crate::fs::hierarchies; 9 | use crate::systemd::utils::is_slice_unit; 10 | use crate::systemd::{ 11 | BLOCK_IO_ACCOUNTING, CPU_ACCOUNTING, DEFAULT_DEPENDENCIES, DEFAULT_DESCRIPTION, DELEGATE, 12 | DESCRIPTION, IO_ACCOUNTING, MEMORY_ACCOUNTING, PIDS, SLICE, TASKS_ACCOUNTING, 13 | TIMEOUT_STOP_USEC, WANTS, 14 | }; 15 | 16 | pub type Property<'a> = (&'a str, ZbusValue<'a>); 17 | 18 | #[derive(Debug, Clone, Default)] 19 | pub struct PropertiesBuilder { 20 | cpu_accounting: Option, 21 | // MemoryAccount is for cgroup v2 as documented in dbus. However, 22 | // "github.com/opencontainer/runc" uses it for all. Shall we follow the 23 | // same way? 24 | memory_accounting: Option, 25 | task_accounting: Option, 26 | // Use IO_ACCOUNTING for cgroup v2 and BLOCK_IO_ACCOUNTING for cgroup v1. 27 | io_accounting: Option, 28 | default_dependencies: Option, 29 | description: Option, 30 | wants: Option, 31 | slice: Option, 32 | delegate: Option, 33 | pids: Option>, 34 | timeout_stop_usec: Option, 35 | } 36 | 37 | impl PropertiesBuilder { 38 | pub fn default_cgroup(slice: &str, unit: &str) -> Self { 39 | let mut builder = Self::default() 40 | .cpu_accounting(true) 41 | .memory_accounting(true) 42 | .task_accounting(true) 43 | .io_accounting(true) 44 | .default_dependencies(false) 45 | .description(format!("{} {}:{}", DEFAULT_DESCRIPTION, slice, unit)); 46 | 47 | if is_slice_unit(unit) { 48 | // If we create a slice, the parent is defined via a Wants=. 49 | builder = builder.wants(slice.to_string()); 50 | } else { 51 | // Otherwise it's a scope, which we put into a Slice=. 52 | builder = builder.slice(slice.to_string()); 53 | // Assume scopes always support delegation (supported since systemd v218). 54 | builder = builder.delegate(true); 55 | } 56 | 57 | builder 58 | } 59 | 60 | pub fn cpu_accounting(mut self, enabled: bool) -> Self { 61 | self.cpu_accounting = Some(enabled); 62 | self 63 | } 64 | 65 | pub fn memory_accounting(mut self, enabled: bool) -> Self { 66 | self.memory_accounting = Some(enabled); 67 | self 68 | } 69 | 70 | pub fn task_accounting(mut self, enabled: bool) -> Self { 71 | self.task_accounting = Some(enabled); 72 | self 73 | } 74 | 75 | pub fn io_accounting(mut self, enabled: bool) -> Self { 76 | self.io_accounting = Some(enabled); 77 | self 78 | } 79 | 80 | pub fn default_dependencies(mut self, enabled: bool) -> Self { 81 | self.default_dependencies = Some(enabled); 82 | self 83 | } 84 | 85 | pub fn description(mut self, desc: String) -> Self { 86 | self.description = Some(desc); 87 | self 88 | } 89 | 90 | pub fn wants(mut self, wants: String) -> Self { 91 | self.wants = Some(wants); 92 | self 93 | } 94 | 95 | pub fn slice(mut self, slice: String) -> Self { 96 | self.slice = Some(slice); 97 | self 98 | } 99 | 100 | pub fn delegate(mut self, enabled: bool) -> Self { 101 | self.delegate = Some(enabled); 102 | self 103 | } 104 | 105 | pub fn pids(mut self, pids: Vec) -> Self { 106 | self.pids = Some(pids); 107 | self 108 | } 109 | 110 | pub fn timeout_stop_usec(mut self, timeout: u64) -> Self { 111 | self.timeout_stop_usec = Some(timeout); 112 | self 113 | } 114 | 115 | pub fn build(self) -> Vec> { 116 | let mut props = vec![]; 117 | 118 | if let Some(cpu_accounting) = self.cpu_accounting { 119 | props.push((CPU_ACCOUNTING, ZbusValue::Bool(cpu_accounting))); 120 | } 121 | 122 | if let Some(memory_accounting) = self.memory_accounting { 123 | props.push((MEMORY_ACCOUNTING, ZbusValue::Bool(memory_accounting))); 124 | } 125 | 126 | if let Some(task_accounting) = self.task_accounting { 127 | props.push((TASKS_ACCOUNTING, ZbusValue::Bool(task_accounting))); 128 | } 129 | 130 | if let Some(io_accounting) = self.io_accounting { 131 | if hierarchies::is_cgroup2_unified_mode() { 132 | props.push((IO_ACCOUNTING, ZbusValue::Bool(io_accounting))); 133 | } else { 134 | props.push((BLOCK_IO_ACCOUNTING, ZbusValue::Bool(io_accounting))); 135 | } 136 | } 137 | 138 | if let Some(default_dependencies) = self.default_dependencies { 139 | props.push((DEFAULT_DEPENDENCIES, ZbusValue::Bool(default_dependencies))); 140 | } 141 | 142 | if let Some(description) = self.description { 143 | props.push((DESCRIPTION, ZbusValue::Str(description.into()))); 144 | } else { 145 | props.push((DESCRIPTION, ZbusValue::Str(DEFAULT_DESCRIPTION.into()))); 146 | } 147 | 148 | if let Some(wants) = self.wants { 149 | props.push((WANTS, ZbusValue::Str(wants.into()))); 150 | } 151 | 152 | if let Some(slice) = self.slice { 153 | props.push((SLICE, ZbusValue::Str(slice.into()))); 154 | } 155 | 156 | if let Some(delegate) = self.delegate { 157 | props.push((DELEGATE, ZbusValue::Bool(delegate))); 158 | } 159 | 160 | if let Some(pids) = self.pids { 161 | props.push((PIDS, ZbusValue::Array(pids.into()))); 162 | } 163 | 164 | if let Some(timeout) = self.timeout_stop_usec { 165 | props.push((TIMEOUT_STOP_USEC, ZbusValue::U64(timeout))); 166 | } 167 | 168 | props 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/systemd/utils.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Ant Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | use crate::systemd::error::{Error, Result}; 7 | use crate::systemd::{SCOPE_SUFFIX, SLICE_SUFFIX}; 8 | 9 | /// Check if a systemd unit name is a slice unit. 10 | pub fn is_slice_unit(name: &str) -> bool { 11 | name.ends_with(SLICE_SUFFIX) 12 | } 13 | 14 | /// Check if a systemd unit name is a scope unit. 15 | pub fn is_scope_unit(name: &str) -> bool { 16 | name.ends_with(SCOPE_SUFFIX) 17 | } 18 | 19 | /// Expand a slice name to a full path in the filesystem. 20 | /// 21 | /// # Arguments 22 | /// 23 | /// * `slice` - A string slice that holds the slice name in the format 24 | /// "xxx-yyy-zzz.slice". 25 | /// 26 | /// # Returns 27 | /// 28 | /// A string that represents the full path of the slice in the filesystem. 29 | /// In the above case, the value would be 30 | /// "xxx.slice/xxx-yyy.slice/xxx-yyy-zzz.slice". 31 | pub fn expand_slice(slice: &str) -> Result { 32 | // Name has to end with ".slice", but can't be just ".slice". 33 | if !slice.ends_with(SLICE_SUFFIX) || slice.len() < SLICE_SUFFIX.len() { 34 | return Err(Error::InvalidArgument); 35 | } 36 | 37 | // Path-separators are not allowed. 38 | if slice.contains('/') { 39 | return Err(Error::InvalidArgument); 40 | } 41 | 42 | let name = slice.trim_end_matches(SLICE_SUFFIX); 43 | 44 | // If input was -.slice, we should just return root now 45 | if name == "-" { 46 | return Ok("".to_string()); 47 | } 48 | 49 | let mut slice_path = String::new(); 50 | let mut prefix = String::new(); 51 | for sub_slice in name.split('-') { 52 | if sub_slice.is_empty() { 53 | return Err(Error::InvalidArgument); 54 | } 55 | 56 | slice_path = format!("{}/{}{}{}", slice_path, prefix, sub_slice, SLICE_SUFFIX); 57 | prefix = format!("{}{}-", prefix, sub_slice); 58 | } 59 | 60 | // We need a relative path, so remove the first slash. 61 | slice_path.remove(0); 62 | 63 | Ok(slice_path) 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use crate::systemd::utils::*; 69 | 70 | #[test] 71 | fn test_is_slice_unit() { 72 | assert!(is_slice_unit("test.slice")); 73 | assert!(!is_slice_unit("test.scope")); 74 | } 75 | 76 | #[test] 77 | fn test_is_scope_unit() { 78 | assert!(is_scope_unit("test.scope")); 79 | assert!(!is_scope_unit("test.slice")); 80 | } 81 | 82 | #[test] 83 | fn test_expand_slice() { 84 | assert_eq!(expand_slice("test.slice").unwrap(), "test.slice"); 85 | assert_eq!( 86 | expand_slice("test-1.slice").unwrap(), 87 | "test.slice/test-1.slice" 88 | ); 89 | assert_eq!( 90 | expand_slice("test-1-test-2.slice").unwrap(), 91 | "test.slice/test-1.slice/test-1-test.slice/test-1-test-2.slice" 92 | ); 93 | assert_eq!( 94 | expand_slice("slice-slice.slice").unwrap(), 95 | "slice.slice/slice-slice.slice" 96 | ); 97 | assert_eq!(expand_slice("-.slice").unwrap(), ""); 98 | assert!(expand_slice("invalid/slice").is_err()); 99 | assert!(expand_slice("invalid-slice").is_err()); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tests/builder.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 And Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! Some simple tests covering the builder pattern for control groups. 8 | use cgroups_rs::fs::blkio::*; 9 | use cgroups_rs::fs::cgroup_builder::*; 10 | use cgroups_rs::fs::cpu::*; 11 | use cgroups_rs::fs::devices::*; 12 | use cgroups_rs::fs::hugetlb::*; 13 | use cgroups_rs::fs::memory::*; 14 | use cgroups_rs::fs::net_cls::*; 15 | use cgroups_rs::fs::pid::*; 16 | use cgroups_rs::fs::*; 17 | 18 | #[test] 19 | pub fn test_cpu_res_build() { 20 | let h = cgroups_rs::fs::hierarchies::auto(); 21 | let cg: Cgroup = CgroupBuilder::new("test_cpu_res_build") 22 | .cpu() 23 | .shares(85) 24 | .done() 25 | .build(h) 26 | .unwrap(); 27 | 28 | { 29 | let cpu: &CpuController = cg.controller_of().unwrap(); 30 | assert!(cpu.shares().is_ok()); 31 | assert_eq!(cpu.shares().unwrap(), 85); 32 | } 33 | 34 | cg.delete().unwrap(); 35 | } 36 | 37 | #[test] 38 | pub fn test_memory_res_build() { 39 | let h = cgroups_rs::fs::hierarchies::auto(); 40 | let cg: Cgroup = CgroupBuilder::new("test_memory_res_build") 41 | .memory() 42 | .kernel_memory_limit(128 * 1024 * 1024) 43 | .swappiness(70) 44 | .memory_hard_limit(1024 * 1024 * 1024) 45 | .done() 46 | .build(h) 47 | .unwrap(); 48 | 49 | { 50 | let c: &MemController = cg.controller_of().unwrap(); 51 | if !c.v2() { 52 | // Note: we don't tests the value of c.kmem_stat().limit_in_bytes because on Linux 53 | // kernel >= 5.16 setting this value is unsupported. 54 | assert_eq!(c.memory_stat().swappiness, 70); 55 | } 56 | assert_eq!(c.memory_stat().limit_in_bytes, 1024 * 1024 * 1024); 57 | } 58 | 59 | cg.delete().unwrap(); 60 | } 61 | 62 | #[test] 63 | pub fn test_pid_res_build() { 64 | let h = cgroups_rs::fs::hierarchies::auto(); 65 | let cg: Cgroup = CgroupBuilder::new("test_pid_res_build") 66 | .pid() 67 | .maximum_number_of_processes(MaxValue::Value(123)) 68 | .done() 69 | .build(h) 70 | .unwrap(); 71 | 72 | { 73 | let c: &PidController = cg.controller_of().unwrap(); 74 | assert!(c.get_pid_max().is_ok()); 75 | assert_eq!(c.get_pid_max().unwrap(), MaxValue::Value(123)); 76 | } 77 | 78 | cg.delete().unwrap(); 79 | } 80 | 81 | #[test] 82 | #[ignore] // ignore this test for now, not sure why my kernel doesn't like it 83 | pub fn test_devices_res_build() { 84 | let h = cgroups_rs::fs::hierarchies::auto(); 85 | let cg: Cgroup = CgroupBuilder::new("test_devices_res_build") 86 | .devices() 87 | .device(1, 6, DeviceType::Char, true, vec![DevicePermissions::Read]) 88 | .done() 89 | .build(h) 90 | .unwrap(); 91 | 92 | { 93 | let c: &DevicesController = cg.controller_of().unwrap(); 94 | assert!(c.allowed_devices().is_ok()); 95 | assert_eq!( 96 | c.allowed_devices().unwrap(), 97 | vec![DeviceResource { 98 | allow: true, 99 | devtype: DeviceType::Char, 100 | major: 1, 101 | minor: 6, 102 | access: vec![DevicePermissions::Read], 103 | }] 104 | ); 105 | } 106 | cg.delete().unwrap(); 107 | } 108 | 109 | #[test] 110 | pub fn test_network_res_build() { 111 | let h = cgroups_rs::fs::hierarchies::auto(); 112 | if h.v2() { 113 | // FIXME add cases for v2 114 | return; 115 | } 116 | let cg: Cgroup = CgroupBuilder::new("test_network_res_build") 117 | .network() 118 | .class_id(1337) 119 | .done() 120 | .build(h) 121 | .unwrap(); 122 | 123 | { 124 | let c: &NetClsController = cg.controller_of().unwrap(); 125 | assert!(c.get_class().is_ok()); 126 | assert_eq!(c.get_class().unwrap(), 1337); 127 | } 128 | cg.delete().unwrap(); 129 | } 130 | 131 | #[test] 132 | pub fn test_hugepages_res_build() { 133 | let h = cgroups_rs::fs::hierarchies::auto(); 134 | if h.v2() { 135 | // FIXME add cases for v2 136 | return; 137 | } 138 | let cg: Cgroup = CgroupBuilder::new("test_hugepages_res_build") 139 | .hugepages() 140 | .limit("2MB".to_string(), 4 * 2 * 1024 * 1024) 141 | .done() 142 | .build(h) 143 | .unwrap(); 144 | 145 | { 146 | let c: &HugeTlbController = cg.controller_of().unwrap(); 147 | assert!(c.limit_in_bytes("2MB").is_ok()); 148 | assert_eq!(c.limit_in_bytes("2MB").unwrap(), 4 * 2 * 1024 * 1024); 149 | } 150 | cg.delete().unwrap(); 151 | } 152 | 153 | #[test] 154 | #[ignore] // high version kernel not support `blkio.weight` 155 | pub fn test_blkio_res_build() { 156 | let h = cgroups_rs::fs::hierarchies::auto(); 157 | let cg: Cgroup = CgroupBuilder::new("test_blkio_res_build") 158 | .blkio() 159 | .weight(100) 160 | .done() 161 | .build(h) 162 | .unwrap(); 163 | 164 | { 165 | let c: &BlkIoController = cg.controller_of().unwrap(); 166 | assert_eq!(c.blkio().weight, 100); 167 | } 168 | cg.delete().unwrap(); 169 | } 170 | -------------------------------------------------------------------------------- /tests/cgroup.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 And Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! Simple unit tests about the control groups system. 8 | use std::process::Command; 9 | use std::thread::sleep; 10 | use std::time::Duration; 11 | 12 | use cgroups_rs::fs::cgroup::{ 13 | CGROUP_MODE_DOMAIN, CGROUP_MODE_DOMAIN_INVALID, CGROUP_MODE_DOMAIN_THREADED, 14 | CGROUP_MODE_THREADED, 15 | }; 16 | use cgroups_rs::fs::memory::MemController; 17 | use cgroups_rs::fs::Controller; 18 | use cgroups_rs::fs::{Cgroup, Subsystem}; 19 | use cgroups_rs::CgroupPid; 20 | 21 | #[test] 22 | fn test_procs_iterator_cgroup() { 23 | let h = cgroups_rs::fs::hierarchies::auto(); 24 | let pid = libc::pid_t::from(nix::unistd::getpid()) as u64; 25 | let cg = Cgroup::new(h, String::from("test_procs_iterator_cgroup")).unwrap(); 26 | { 27 | // Add a task to the control group. 28 | cg.add_task_by_tgid(CgroupPid::from(pid)).unwrap(); 29 | 30 | let mut procs = cg.procs().into_iter(); 31 | // Verify that the task is indeed in the xcontrol group 32 | assert_eq!(procs.next(), Some(CgroupPid::from(pid))); 33 | assert_eq!(procs.next(), None); 34 | 35 | // Now, try removing it. 36 | cg.remove_task_by_tgid(CgroupPid::from(pid)).unwrap(); 37 | procs = cg.procs().into_iter(); 38 | 39 | // Verify that it was indeed removed. 40 | assert_eq!(procs.next(), None); 41 | } 42 | cg.delete().unwrap(); 43 | } 44 | 45 | #[test] 46 | fn test_tasks_iterator_cgroup_v1() { 47 | if cgroups_rs::fs::hierarchies::is_cgroup2_unified_mode() { 48 | return; 49 | } 50 | let h = cgroups_rs::fs::hierarchies::auto(); 51 | let pid = libc::pid_t::from(nix::unistd::getpid()) as u64; 52 | let cg = Cgroup::new(h, String::from("test_tasks_iterator_cgroup_v1")).unwrap(); 53 | { 54 | // Add a task to the control group. 55 | cg.add_task(CgroupPid::from(pid)).unwrap(); 56 | 57 | let mut tasks = cg.tasks().into_iter(); 58 | // Verify that the task is indeed in the xcontrol group 59 | assert_eq!(tasks.next(), Some(CgroupPid::from(pid))); 60 | assert_eq!(tasks.next(), None); 61 | 62 | // Now, try removing it. 63 | cg.remove_task(CgroupPid::from(pid)).unwrap(); 64 | tasks = cg.tasks().into_iter(); 65 | 66 | // Verify that it was indeed removed. 67 | assert_eq!(tasks.next(), None); 68 | } 69 | cg.delete().unwrap(); 70 | } 71 | 72 | #[test] 73 | fn test_tasks_iterator_cgroup_threaded_mode() { 74 | if !cgroups_rs::fs::hierarchies::is_cgroup2_unified_mode() { 75 | return; 76 | } 77 | let pid = libc::pid_t::from(nix::unistd::getpid()) as u64; 78 | let cg = Cgroup::new( 79 | cgroups_rs::fs::hierarchies::auto(), 80 | String::from("test_tasks_iterator_cgroup_threaded_mode"), 81 | ) 82 | .unwrap(); 83 | let cg_threaded_sub1 = Cgroup::new_with_specified_controllers( 84 | cgroups_rs::fs::hierarchies::auto(), 85 | String::from("test_tasks_iterator_cgroup_threaded_mode/threaded_sub1"), 86 | Some(vec![String::from("cpuset"), String::from("cpu")]), 87 | ) 88 | .unwrap(); 89 | let cg_threaded_sub2 = Cgroup::new_with_specified_controllers( 90 | cgroups_rs::fs::hierarchies::auto(), 91 | String::from("test_tasks_iterator_cgroup_threaded_mode/threaded_sub2"), 92 | Some(vec![String::from("cpuset"), String::from("cpu")]), 93 | ) 94 | .unwrap(); 95 | { 96 | // Verify that cgroup type of the control group is domain mode. 97 | assert_eq!(cg.get_cgroup_type().unwrap(), CGROUP_MODE_DOMAIN); 98 | 99 | // Set cgroup type of the sub-control group is thread mode. 100 | cg_threaded_sub1 101 | .set_cgroup_type(CGROUP_MODE_THREADED) 102 | .unwrap(); 103 | // Verify that cgroup type of the sub-control group is thread mode. 104 | assert_eq!( 105 | cg_threaded_sub1.get_cgroup_type().unwrap(), 106 | CGROUP_MODE_THREADED 107 | ); 108 | // Verify that the cgroup type of the sub-control group that does 109 | // not set the cgroup type is domain invalid mode. 110 | assert_eq!( 111 | cg_threaded_sub2.get_cgroup_type().unwrap(), 112 | CGROUP_MODE_DOMAIN_INVALID 113 | ); 114 | // Verify whether the cgroup type of the parent control group of 115 | // the control group whose cgroup type is set to thread mode is 116 | // domain thread mode. 117 | assert_eq!(cg.get_cgroup_type().unwrap(), CGROUP_MODE_DOMAIN_THREADED); 118 | 119 | // Set cgroup type of the sub-control group is thread mode. 120 | cg_threaded_sub2 121 | .set_cgroup_type(CGROUP_MODE_THREADED) 122 | .unwrap(); 123 | // Verify that cgroup type of the sub-control group is thread mode. 124 | assert_eq!( 125 | cg_threaded_sub2.get_cgroup_type().unwrap(), 126 | CGROUP_MODE_THREADED 127 | ); 128 | 129 | // Add a proc to the control group. 130 | cg.add_task_by_tgid(CgroupPid::from(pid)).unwrap(); 131 | 132 | let mut procs = cg.procs().into_iter(); 133 | // Verify that the task is indeed in the x control group 134 | assert_eq!(procs.next(), Some(CgroupPid::from(pid))); 135 | assert_eq!(procs.next(), None); 136 | 137 | // Add a task to the sub control group. 138 | cg_threaded_sub1.add_task(CgroupPid::from(pid)).unwrap(); 139 | 140 | let mut tasks = cg_threaded_sub1.tasks().into_iter(); 141 | // Verify that the task is indeed in the xcontrol group 142 | assert_eq!(tasks.next(), Some(CgroupPid::from(pid))); 143 | assert_eq!(tasks.next(), None); 144 | 145 | // Now, try move it to parent. 146 | cg_threaded_sub1 147 | .move_task_to_parent(CgroupPid::from(pid)) 148 | .unwrap(); 149 | tasks = cg_threaded_sub1.tasks().into_iter(); 150 | 151 | // Verify that it was indeed removed. 152 | assert_eq!(tasks.next(), None); 153 | 154 | // Now, try removing it. 155 | cg.remove_task_by_tgid(CgroupPid::from(pid)).unwrap(); 156 | procs = cg.procs().into_iter(); 157 | 158 | // Verify that it was indeed removed. 159 | assert_eq!(procs.next(), None); 160 | } 161 | cg_threaded_sub1.delete().unwrap(); 162 | cg_threaded_sub2.delete().unwrap(); 163 | cg.delete().unwrap(); 164 | } 165 | 166 | #[test] 167 | fn test_kill_cgroup() { 168 | if !cgroups_rs::fs::hierarchies::is_cgroup2_unified_mode() { 169 | return; 170 | } 171 | let h = cgroups_rs::fs::hierarchies::auto(); 172 | let cg = Cgroup::new(h, String::from("test_kill_cgroup")).unwrap(); 173 | { 174 | // Spawn a proc, don't want to getpid(2) here. 175 | let mut child = Command::new("sleep").arg("infinity").spawn().unwrap(); 176 | cg.add_task_by_tgid(CgroupPid::from(child.id() as u64)) 177 | .unwrap(); 178 | 179 | let cg_procs = cg.procs(); 180 | assert_eq!(cg_procs.len(), 1_usize); 181 | 182 | // Now kill and wait on the proc. 183 | cg.kill().unwrap(); 184 | 185 | let mut tries = 0; 186 | let status: Option = loop { 187 | match child.try_wait() { 188 | Ok(Some(status)) => { 189 | break Some(status); 190 | } 191 | Ok(None) => { 192 | if tries > 3 { 193 | break None; 194 | } 195 | sleep(Duration::from_millis(100)); 196 | tries += 1; 197 | } 198 | Err(e) => { 199 | child.kill().unwrap(); 200 | panic!("error attempting to wait: {}", e); 201 | } 202 | } 203 | }; 204 | assert!(status.is_some()); 205 | } 206 | cg.delete().unwrap(); 207 | } 208 | 209 | #[test] 210 | fn test_cgroup_with_relative_paths() { 211 | if cgroups_rs::fs::hierarchies::is_cgroup2_unified_mode() { 212 | return; 213 | } 214 | let h = cgroups_rs::fs::hierarchies::auto(); 215 | let cgroup_root = h.root(); 216 | let cgroup_name = "test_cgroup_with_relative_paths"; 217 | 218 | let cg = Cgroup::load(h, String::from(cgroup_name)); 219 | { 220 | let subsystems = cg.subsystems(); 221 | subsystems.iter().for_each(|sub| match sub { 222 | Subsystem::Pid(c) => { 223 | let cgroup_path = c.path().to_str().unwrap(); 224 | let relative_path = "/pids/"; 225 | // cgroup_path = cgroup_root + relative_path + cgroup_name 226 | assert_eq!( 227 | cgroup_path, 228 | format!( 229 | "{}{}{}", 230 | cgroup_root.to_str().unwrap(), 231 | relative_path, 232 | cgroup_name 233 | ) 234 | ); 235 | } 236 | Subsystem::Mem(c) => { 237 | let cgroup_path = c.path().to_str().unwrap(); 238 | // cgroup_path = cgroup_root + relative_path + cgroup_name 239 | assert_eq!( 240 | cgroup_path, 241 | format!("{}/memory/{}", cgroup_root.to_str().unwrap(), cgroup_name) 242 | ); 243 | } 244 | _ => {} 245 | }); 246 | } 247 | cg.delete().unwrap(); 248 | } 249 | 250 | #[test] 251 | fn test_cgroup_v2() { 252 | if !cgroups_rs::fs::hierarchies::is_cgroup2_unified_mode() { 253 | return; 254 | } 255 | let h = cgroups_rs::fs::hierarchies::auto(); 256 | let cg = Cgroup::new(h, String::from("test_v2")).unwrap(); 257 | 258 | let mem_controller: &MemController = cg.controller_of().unwrap(); 259 | let (mem, swp, rev) = (4 * 1024 * 1000, 2 * 1024 * 1000, 1024 * 1000); 260 | 261 | mem_controller.set_limit(mem).unwrap(); 262 | mem_controller.set_memswap_limit(swp).unwrap(); 263 | mem_controller.set_soft_limit(rev).unwrap(); 264 | 265 | let memory_stat = mem_controller.memory_stat(); 266 | println!("memory_stat {:?}", memory_stat); 267 | assert_eq!(mem, memory_stat.limit_in_bytes); 268 | assert_eq!(rev, memory_stat.soft_limit_in_bytes); 269 | 270 | let memswap = mem_controller.memswap(); 271 | println!("memswap {:?}", memswap); 272 | assert_eq!(swp, memswap.limit_in_bytes); 273 | 274 | cg.delete().unwrap(); 275 | } 276 | -------------------------------------------------------------------------------- /tests/cpu.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 And Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | //! Simple unit tests about the CPU control groups system. 7 | use cgroups_rs::fs::cpu::CpuController; 8 | use cgroups_rs::fs::Cgroup; 9 | 10 | #[test] 11 | fn test_cfs_quota_and_periods() { 12 | let h = cgroups_rs::fs::hierarchies::auto(); 13 | let cg = Cgroup::new(h, String::from("test_cfs_quota_and_periods")).unwrap(); 14 | 15 | let cpu_controller: &CpuController = cg.controller_of().unwrap(); 16 | 17 | let current_quota = cpu_controller.cfs_quota().unwrap(); 18 | let current_peroid = cpu_controller.cfs_period().unwrap(); 19 | 20 | // verify default value 21 | // The default is “max 100000”. 22 | assert_eq!(-1, current_quota); 23 | assert_eq!(100000, current_peroid); 24 | 25 | // case 1 set quota 26 | let _ = cpu_controller.set_cfs_quota(2000); 27 | 28 | let current_quota = cpu_controller.cfs_quota().unwrap(); 29 | let current_peroid = cpu_controller.cfs_period().unwrap(); 30 | assert_eq!(2000, current_quota); 31 | assert_eq!(100000, current_peroid); 32 | 33 | // case 2 set period 34 | cpu_controller.set_cfs_period(1000000).unwrap(); 35 | let current_quota = cpu_controller.cfs_quota().unwrap(); 36 | let current_peroid = cpu_controller.cfs_period().unwrap(); 37 | assert_eq!(2000, current_quota); 38 | assert_eq!(1000000, current_peroid); 39 | 40 | // case 3 set both quota and period 41 | cpu_controller 42 | .set_cfs_quota_and_period(Some(5000), Some(100000)) 43 | .unwrap(); 44 | 45 | let current_quota = cpu_controller.cfs_quota().unwrap(); 46 | let current_peroid = cpu_controller.cfs_period().unwrap(); 47 | assert_eq!(5000, current_quota); 48 | assert_eq!(100000, current_peroid); 49 | 50 | // case 4 set both quota and period, set quota to -1 51 | cpu_controller 52 | .set_cfs_quota_and_period(Some(-1), None) 53 | .unwrap(); 54 | 55 | let current_quota = cpu_controller.cfs_quota().unwrap(); 56 | let current_peroid = cpu_controller.cfs_period().unwrap(); 57 | assert_eq!(-1, current_quota); 58 | assert_eq!(100000, current_peroid); 59 | 60 | cg.delete().unwrap(); 61 | } 62 | -------------------------------------------------------------------------------- /tests/cpuset.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 And Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | use std::fs; 7 | 8 | use cgroups_rs::fs::cpuset::CpuSetController; 9 | use cgroups_rs::fs::error::ErrorKind; 10 | use cgroups_rs::fs::Cgroup; 11 | use cgroups_rs::CgroupPid; 12 | 13 | #[test] 14 | fn test_cpuset_memory_pressure_root_cg() { 15 | let h = cgroups_rs::fs::hierarchies::auto(); 16 | let cg = Cgroup::new(h, String::from("test_cpuset_memory_pressure_root_cg")).unwrap(); 17 | { 18 | let cpuset: &CpuSetController = cg.controller_of().unwrap(); 19 | 20 | // This is not a root control group, so it should fail via InvalidOperation. 21 | let res = cpuset.set_enable_memory_pressure(true); 22 | assert_eq!(res.unwrap_err().kind(), &ErrorKind::InvalidOperation); 23 | } 24 | cg.delete().unwrap(); 25 | } 26 | 27 | #[test] 28 | fn test_cpuset_set_cpus() { 29 | let h = cgroups_rs::fs::hierarchies::auto(); 30 | let cg = Cgroup::new(h, String::from("test_cpuset_set_cpus")).unwrap(); 31 | { 32 | let cpuset: &CpuSetController = cg.controller_of().unwrap(); 33 | 34 | let set = cpuset.cpuset(); 35 | if cg.v2() { 36 | assert_eq!(0, set.cpus.len()); 37 | } else { 38 | // for cgroup v1, cpuset is copied from parent. 39 | assert!(!set.cpus.is_empty()); 40 | } 41 | 42 | // 0 43 | let r = cpuset.set_cpus("0"); 44 | assert!(r.is_ok()); 45 | 46 | let set = cpuset.cpuset(); 47 | assert_eq!(1, set.cpus.len()); 48 | assert_eq!((0, 0), set.cpus[0]); 49 | 50 | // all cpus in system 51 | let cpus = fs::read_to_string("/sys/fs/cgroup/cpuset.cpus.effective").unwrap_or_default(); 52 | let cpus = cpus.trim(); 53 | if !cpus.is_empty() { 54 | let r = cpuset.set_cpus(cpus); 55 | assert!(r.is_ok()); 56 | let set = cpuset.cpuset(); 57 | assert_eq!(1, set.cpus.len()); 58 | assert_eq!(format!("{}-{}", set.cpus[0].0, set.cpus[0].1), cpus); 59 | } 60 | } 61 | cg.delete().unwrap(); 62 | } 63 | 64 | #[test] 65 | fn test_cpuset_set_cpus_add_task() { 66 | let h = cgroups_rs::fs::hierarchies::auto(); 67 | let cg = Cgroup::new(h, String::from("test_cpuset_set_cpus_add_task/sub-dir")).unwrap(); 68 | 69 | let cpuset: &CpuSetController = cg.controller_of().unwrap(); 70 | let set = cpuset.cpuset(); 71 | if cg.v2() { 72 | assert_eq!(0, set.cpus.len()); 73 | } else { 74 | // for cgroup v1, cpuset is copied from parent. 75 | assert!(!set.cpus.is_empty()); 76 | } 77 | 78 | // Add a task to the control group. 79 | let pid_i = libc::pid_t::from(nix::unistd::getpid()) as u64; 80 | let _ = cg.add_task_by_tgid(CgroupPid::from(pid_i)); 81 | let tasks = cg.tasks(); 82 | assert!(!tasks.is_empty()); 83 | println!("tasks after added: {:?}", tasks); 84 | 85 | // remove task 86 | cg.remove_task_by_tgid(CgroupPid::from(pid_i)).unwrap(); 87 | let tasks = cg.tasks(); 88 | println!("tasks after deleted: {:?}", tasks); 89 | assert_eq!(0, tasks.len()); 90 | 91 | cg.delete().unwrap(); 92 | } 93 | -------------------------------------------------------------------------------- /tests/devices.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 And Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! Integration tests about the devices subsystem 8 | 9 | use cgroups_rs::fs::devices::{DevicePermissions, DeviceType, DevicesController}; 10 | use cgroups_rs::fs::{Cgroup, DeviceResource}; 11 | 12 | #[test] 13 | fn test_devices_parsing() { 14 | // now only v2 15 | if cgroups_rs::fs::hierarchies::is_cgroup2_unified_mode() { 16 | return; 17 | } 18 | 19 | let h = cgroups_rs::fs::hierarchies::auto(); 20 | let cg = Cgroup::new(h, String::from("test_devices_parsing")).unwrap(); 21 | { 22 | let devices: &DevicesController = cg.controller_of().unwrap(); 23 | 24 | // Deny access to all devices first 25 | devices 26 | .deny_device( 27 | DeviceType::All, 28 | -1, 29 | -1, 30 | &[ 31 | DevicePermissions::Read, 32 | DevicePermissions::Write, 33 | DevicePermissions::MkNod, 34 | ], 35 | ) 36 | .unwrap(); 37 | // Acquire the list of allowed devices after we denied all 38 | let allowed_devices = devices.allowed_devices(); 39 | // Verify that there are no devices that we can access. 40 | assert!(allowed_devices.is_ok()); 41 | assert_eq!(allowed_devices.unwrap(), Vec::new()); 42 | 43 | // Now add mknod access to /dev/null device 44 | devices 45 | .allow_device(DeviceType::Char, 1, 3, &[DevicePermissions::MkNod]) 46 | .unwrap(); 47 | let allowed_devices = devices.allowed_devices(); 48 | assert!(allowed_devices.is_ok()); 49 | let allowed_devices = allowed_devices.unwrap(); 50 | assert_eq!(allowed_devices.len(), 1); 51 | assert_eq!( 52 | allowed_devices[0], 53 | DeviceResource { 54 | allow: true, 55 | devtype: DeviceType::Char, 56 | major: 1, 57 | minor: 3, 58 | access: vec![DevicePermissions::MkNod], 59 | } 60 | ); 61 | 62 | // Now deny, this device explicitly. 63 | devices 64 | .deny_device(DeviceType::Char, 1, 3, &DevicePermissions::all()) 65 | .unwrap(); 66 | // Finally, check that. 67 | let allowed_devices = devices.allowed_devices(); 68 | // Verify that there are no devices that we can access. 69 | assert!(allowed_devices.is_ok()); 70 | assert_eq!(allowed_devices.unwrap(), Vec::new()); 71 | } 72 | cg.delete().unwrap(); 73 | } 74 | -------------------------------------------------------------------------------- /tests/hugetlb.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 And Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | //! Integration tests about the hugetlb subsystem 7 | use cgroups_rs::fs::error::*; 8 | use cgroups_rs::fs::hugetlb::{self, HugeTlbController}; 9 | use cgroups_rs::fs::Cgroup; 10 | use std::fs; 11 | 12 | #[test] 13 | fn test_hugetlb_sizes() { 14 | // now only v2 15 | if cgroups_rs::fs::hierarchies::is_cgroup2_unified_mode() { 16 | return; 17 | } 18 | 19 | let h = cgroups_rs::fs::hierarchies::auto(); 20 | let cg = Cgroup::new(h, String::from("test_hugetlb_sizes")).unwrap(); 21 | { 22 | let hugetlb_controller: &HugeTlbController = cg.controller_of().unwrap(); 23 | let _ = hugetlb_controller.get_sizes(); 24 | 25 | // test sizes count 26 | let sizes = hugetlb_controller.get_sizes(); 27 | let sizes_count = fs::read_dir(hugetlb::HUGEPAGESIZE_DIR).unwrap().count(); 28 | assert_eq!(sizes.len(), sizes_count); 29 | 30 | for size in sizes { 31 | let supported = hugetlb_controller.size_supported(&size); 32 | assert!(supported); 33 | assert_no_error(hugetlb_controller.failcnt(&size)); 34 | assert_no_error(hugetlb_controller.limit_in_bytes(&size)); 35 | assert_no_error(hugetlb_controller.usage_in_bytes(&size)); 36 | assert_no_error(hugetlb_controller.max_usage_in_bytes(&size)); 37 | } 38 | } 39 | cg.delete().unwrap(); 40 | } 41 | 42 | fn assert_no_error(r: Result) { 43 | assert!(r.is_ok()) 44 | } 45 | -------------------------------------------------------------------------------- /tests/memory.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 And Group 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 or MIT 4 | // 5 | 6 | //! Integration tests about the hugetlb subsystem 7 | use cgroups_rs::fs::memory::{MemController, SetMemory}; 8 | use cgroups_rs::fs::{Cgroup, Controller, MaxValue}; 9 | 10 | #[test] 11 | fn test_disable_oom_killer() { 12 | let h = cgroups_rs::fs::hierarchies::auto(); 13 | let cg = Cgroup::new(h, String::from("test_disable_oom_killer")).unwrap(); 14 | { 15 | let mem_controller: &MemController = cg.controller_of().unwrap(); 16 | 17 | // before disable 18 | let m = mem_controller.memory_stat(); 19 | assert!(!m.oom_control.oom_kill_disable); 20 | 21 | // now only v1 22 | if !mem_controller.v2() { 23 | // disable oom killer 24 | let r = mem_controller.disable_oom_killer(); 25 | assert!(r.is_ok()); 26 | 27 | // after disable 28 | let m = mem_controller.memory_stat(); 29 | assert!(m.oom_control.oom_kill_disable); 30 | } 31 | } 32 | cg.delete().unwrap(); 33 | } 34 | 35 | #[test] 36 | fn set_kmem_limit_v1() { 37 | let h = cgroups_rs::fs::hierarchies::auto(); 38 | if h.v2() { 39 | return; 40 | } 41 | 42 | let cg = Cgroup::new(h, String::from("set_kmem_limit_v1")).unwrap(); 43 | { 44 | let mem_controller: &MemController = cg.controller_of().unwrap(); 45 | mem_controller.set_kmem_limit(1).unwrap(); 46 | } 47 | cg.delete().unwrap(); 48 | } 49 | 50 | #[test] 51 | fn set_mem_v2() { 52 | let h = cgroups_rs::fs::hierarchies::auto(); 53 | if !h.v2() { 54 | return; 55 | } 56 | 57 | let cg = Cgroup::new(h, String::from("set_mem_v2")).unwrap(); 58 | { 59 | let mem_controller: &MemController = cg.controller_of().unwrap(); 60 | 61 | // before disable 62 | let m = mem_controller.get_mem().unwrap(); 63 | // case 1: get default value 64 | assert_eq!(m.low, Some(MaxValue::Value(0))); 65 | assert_eq!(m.min, Some(MaxValue::Value(0))); 66 | assert_eq!(m.high, Some(MaxValue::Max)); 67 | assert_eq!(m.max, Some(MaxValue::Max)); 68 | 69 | // case 2: set parts 70 | let m = SetMemory { 71 | low: Some(MaxValue::Value(1024 * 1024 * 2)), 72 | high: Some(MaxValue::Value(1024 * 1024 * 1024 * 2)), 73 | min: Some(MaxValue::Value(1024 * 1024 * 3)), 74 | max: None, 75 | }; 76 | let r = mem_controller.set_mem(m); 77 | assert!(r.is_ok()); 78 | 79 | let m = mem_controller.get_mem().unwrap(); 80 | // get 81 | assert_eq!(m.low, Some(MaxValue::Value(1024 * 1024 * 2))); 82 | assert_eq!(m.min, Some(MaxValue::Value(1024 * 1024 * 3))); 83 | assert_eq!(m.high, Some(MaxValue::Value(1024 * 1024 * 1024 * 2))); 84 | assert_eq!(m.max, Some(MaxValue::Max)); 85 | 86 | // case 3: set parts 87 | let m = SetMemory { 88 | max: Some(MaxValue::Value(1024 * 1024 * 1024 * 2)), 89 | min: Some(MaxValue::Value(1024 * 1024 * 4)), 90 | high: Some(MaxValue::Max), 91 | low: None, 92 | }; 93 | let r = mem_controller.set_mem(m); 94 | assert!(r.is_ok()); 95 | 96 | let m = mem_controller.get_mem().unwrap(); 97 | // get 98 | assert_eq!(m.low, Some(MaxValue::Value(1024 * 1024 * 2))); 99 | assert_eq!(m.min, Some(MaxValue::Value(1024 * 1024 * 4))); 100 | assert_eq!(m.max, Some(MaxValue::Value(1024 * 1024 * 1024 * 2))); 101 | assert_eq!(m.high, Some(MaxValue::Max)); 102 | } 103 | 104 | cg.delete().unwrap(); 105 | } 106 | -------------------------------------------------------------------------------- /tests/pids.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 And Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! Integration tests about the pids subsystem 8 | use cgroups_rs::fs::pid::PidController; 9 | use cgroups_rs::fs::{Cgroup, Controller, MaxValue}; 10 | use libc::pid_t; 11 | use nix::sys::wait::{waitpid, WaitStatus}; 12 | use nix::unistd::{fork, ForkResult}; 13 | 14 | #[test] 15 | fn create_and_delete_cgroup() { 16 | let h = cgroups_rs::fs::hierarchies::auto(); 17 | let cg = Cgroup::new(h, String::from("create_and_delete_cgroup")).unwrap(); 18 | { 19 | let pidcontroller: &PidController = cg.controller_of().unwrap(); 20 | pidcontroller.set_pid_max(MaxValue::Value(1337)).unwrap(); 21 | let max = pidcontroller.get_pid_max(); 22 | assert!(max.is_ok()); 23 | assert_eq!(max.unwrap(), MaxValue::Value(1337)); 24 | } 25 | cg.delete().unwrap(); 26 | } 27 | 28 | #[test] 29 | fn test_pids_current_is_zero() { 30 | let h = cgroups_rs::fs::hierarchies::auto(); 31 | let cg = Cgroup::new(h, String::from("test_pids_current_is_zero")).unwrap(); 32 | { 33 | let pidcontroller: &PidController = cg.controller_of().unwrap(); 34 | let current = pidcontroller.get_pid_current(); 35 | assert_eq!(current.unwrap(), 0); 36 | } 37 | cg.delete().unwrap(); 38 | } 39 | 40 | #[test] 41 | fn test_pids_events_is_zero() { 42 | let h = cgroups_rs::fs::hierarchies::auto(); 43 | let cg = Cgroup::new(h, String::from("test_pids_events_is_zero")).unwrap(); 44 | { 45 | let pidcontroller: &PidController = cg.controller_of().unwrap(); 46 | let events = pidcontroller.get_pid_events(); 47 | assert!(events.is_ok()); 48 | assert_eq!(events.unwrap(), 0); 49 | } 50 | cg.delete().unwrap(); 51 | } 52 | 53 | #[test] 54 | fn test_pid_events_is_not_zero() { 55 | let h = cgroups_rs::fs::hierarchies::auto(); 56 | let cg = Cgroup::new(h, String::from("test_pid_events_is_not_zero")).unwrap(); 57 | { 58 | let pids: &PidController = cg.controller_of().unwrap(); 59 | let before = pids.get_pid_events(); 60 | let before = before.unwrap(); 61 | 62 | match unsafe { fork() } { 63 | Ok(ForkResult::Parent { child, .. }) => { 64 | // move the process into the control group 65 | let _ = pids.add_task_by_tgid(&(pid_t::from(child) as u64).into()); 66 | 67 | println!("added task to cg: {:?}", child); 68 | 69 | // Set limit to one 70 | let _ = pids.set_pid_max(MaxValue::Value(1)); 71 | println!("current pid.max = {:?}", pids.get_pid_max()); 72 | 73 | // wait on the child 74 | let res = waitpid(child, None); 75 | if let Ok(WaitStatus::Exited(_, e)) = res { 76 | assert_eq!(e, 0i32); 77 | } else { 78 | panic!("found result: {:?}", res); 79 | } 80 | 81 | // Check pids.events 82 | let events = pids.get_pid_events(); 83 | assert!(events.is_ok()); 84 | assert_eq!(events.unwrap(), before + 1); 85 | } 86 | Ok(ForkResult::Child) => loop { 87 | let pids_max = pids.get_pid_max(); 88 | if pids_max.is_ok() && pids_max.unwrap() == MaxValue::Value(1) { 89 | if unsafe { fork() }.is_err() { 90 | unsafe { libc::exit(0) }; 91 | } else { 92 | unsafe { libc::exit(1) }; 93 | } 94 | } 95 | }, 96 | Err(_) => panic!("failed to fork"), 97 | } 98 | } 99 | cg.delete().unwrap(); 100 | } 101 | -------------------------------------------------------------------------------- /tests/resources.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Levente Kurusa 2 | // Copyright (c) 2020 And Group 3 | // 4 | // SPDX-License-Identifier: Apache-2.0 or MIT 5 | // 6 | 7 | //! Integration test about setting resources using `apply()` 8 | use cgroups_rs::fs::pid::PidController; 9 | use cgroups_rs::fs::{Cgroup, MaxValue, PidResources, Resources}; 10 | 11 | #[test] 12 | fn pid_resources() { 13 | let h = cgroups_rs::fs::hierarchies::auto(); 14 | let cg = Cgroup::new(h, String::from("pid_resources")).unwrap(); 15 | { 16 | let res = Resources { 17 | pid: PidResources { 18 | maximum_number_of_processes: Some(MaxValue::Value(512)), 19 | }, 20 | ..Default::default() 21 | }; 22 | cg.apply(&res).unwrap(); 23 | 24 | // verify 25 | let pidcontroller: &PidController = cg.controller_of().unwrap(); 26 | let pid_max = pidcontroller.get_pid_max(); 27 | assert!(pid_max.is_ok()); 28 | assert_eq!(pid_max.unwrap(), MaxValue::Value(512)); 29 | } 30 | cg.delete().unwrap(); 31 | } 32 | -------------------------------------------------------------------------------- /tools/create_cgroup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2018 Levente Kurusa 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 or MIT 6 | # 7 | 8 | CONTROL_GROUPS=`cargo test -- --list 2>/dev/null | egrep 'test$' | egrep -v '^src' | cut -d':' -f1` 9 | 10 | echo This script will create a control group in every subsystem of the V1 hierarchy. 11 | echo For this, we will need your sudo privileges. Please do not trust this shell script and have a look to check that it does something that you are okay with. 12 | sudo -v 13 | 14 | for i in ${CONTROL_GROUPS} 15 | do sudo mkdir -p /sys/fs/cgroup/{blkio,cpu,cpuacct,cpuset,devices,freezer,hugetlb,memory,net_cls,net_prio,perf_event,pids}/$i/ 16 | 17 | done 18 | echo 19 | echo We will now set up permissions... 20 | echo 21 | 22 | for i in ${CONTROL_GROUPS} 23 | do sudo chown -R ${USER} /sys/fs/cgroup/{blkio,cpu,cpuacct,cpuset,devices,freezer,hugetlb,memory,net_cls,net_prio,perf_event,pids}/$i/ 24 | done 25 | -------------------------------------------------------------------------------- /tools/delete_cgroup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2018 Levente Kurusa 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 or MIT 6 | # 7 | 8 | CONTROL_GROUPS=`cargo test -- --list 2>/dev/null | egrep 'test$' | egrep -v '^src' | cut -d':' -f1` 9 | 10 | echo This script will delete the control groups created by the create_cgroup.sh shell script. 11 | echo 12 | echo It may spit out some errors, but that is fine. 13 | echo 14 | echo For this, we will need your sudo privileges. Please do not trust this shell script and have a look to check that it does something that you are okay with. 15 | sudo -v 16 | 17 | for i in ${CONTROL_GROUPS} 18 | do sudo rmdir /sys/fs/cgroup/{blkio,cpu,cpuacct,cpuset,devices,freezer,hugetlb,memory,net_cls,net_prio,perf_event,pids}/$i/ 19 | done 20 | --------------------------------------------------------------------------------