Merge pull request from mdcssw/main

Implemented `chcpu`
This commit is contained in:
Sylvestre Ledru 2025-03-21 10:09:12 +01:00 committed by GitHub
commit f8e425b378
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 860 additions and 99 deletions

4
.gitignore vendored

@ -1 +1,3 @@
/target
syntax: glob
/target/

219
Cargo.lock generated

@ -84,9 +84,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bitflags"
version = "2.8.0"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "block-buffer"
@ -103,17 +103,11 @@ version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.2.13"
version = "1.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda"
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
dependencies = [
"shlex",
]
@ -261,9 +255,9 @@ dependencies = [
[[package]]
name = "deranged"
version = "0.3.11"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
dependencies = [
"powerfmt",
]
@ -298,15 +292,15 @@ dependencies = [
[[package]]
name = "either"
version = "1.13.0"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "equivalent"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
@ -315,7 +309,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -336,14 +330,14 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi",
"windows-targets 0.52.6",
]
[[package]]
@ -389,9 +383,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.7.1"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
dependencies = [
"equivalent",
"hashbrown",
@ -405,9 +399,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itoa"
version = "1.0.14"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "js-sys"
@ -439,9 +433,9 @@ checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413"
[[package]]
name = "log"
version = "0.4.25"
version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
[[package]]
name = "md-5"
@ -521,9 +515,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]]
name = "once_cell"
version = "1.20.3"
version = "1.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
[[package]]
name = "os_display"
@ -600,11 +594,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.20"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy 0.7.35",
"zerocopy",
]
[[package]]
@ -619,9 +613,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.93"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
@ -650,13 +644,19 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.38"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]]
name = "rand"
version = "0.8.5"
@ -673,8 +673,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
dependencies = [
"rand_chacha",
"rand_core 0.9.0",
"zerocopy 0.8.17",
"rand_core 0.9.3",
"zerocopy",
]
[[package]]
@ -684,7 +684,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.0",
"rand_core 0.9.3",
]
[[package]]
@ -695,14 +695,19 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "rand_core"
version = "0.9.0"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom",
"zerocopy 0.8.17",
]
[[package]]
name = "rangemap"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684"
[[package]]
name = "rayon"
version = "1.10.0"
@ -777,33 +782,33 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.4.15",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
name = "rustix"
version = "1.0.0"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17f8dcd64f141950290e45c99f7710ede1b600297c91818bb30b3667c0f45dc0"
checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys 0.9.3",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
name = "rustversion"
version = "1.0.19"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "ryu"
version = "1.0.19"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "serde"
@ -874,15 +879,21 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.98"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syscall-numbers"
version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e88dcf8be2fd556b3cebd02689c424dced834317c7e38328eadfcfcab00b785"
[[package]]
name = "sysinfo"
version = "0.33.1"
@ -906,17 +917,17 @@ dependencies = [
"fastrand",
"getrandom",
"once_cell",
"rustix 1.0.0",
"windows-sys 0.52.0",
"rustix 1.0.3",
"windows-sys 0.59.0",
]
[[package]]
name = "terminal_size"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9"
checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed"
dependencies = [
"rustix 0.38.44",
"rustix 1.0.3",
"windows-sys 0.59.0",
]
@ -933,10 +944,30 @@ dependencies = [
]
[[package]]
name = "time"
version = "0.3.37"
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.3.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d9c75b47bdff86fa3334a3db91356b8d7d86a9b839dab7d0bdc5c3d3a077618"
dependencies = [
"deranged",
"itoa",
@ -951,15 +982,15 @@ dependencies = [
[[package]]
name = "time-core"
version = "0.1.2"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
[[package]]
name = "time-macros"
version = "0.2.19"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
checksum = "29aa485584182073ed57fd5004aa09c371f021325014694e432313345865fd04"
dependencies = [
"num-conv",
"time-core",
@ -973,9 +1004,9 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]]
name = "unicode-ident"
version = "1.0.16"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unicode-linebreak"
@ -1023,6 +1054,7 @@ dependencies = [
"tempfile",
"textwrap",
"uu_blockdev",
"uu_chcpu",
"uu_ctrlaltdel",
"uu_dmesg",
"uu_fsfreeze",
@ -1051,6 +1083,18 @@ dependencies = [
"uucore",
]
[[package]]
name = "uu_chcpu"
version = "0.0.1"
dependencies = [
"clap",
"libc",
"rangemap",
"syscall-numbers",
"thiserror",
"uucore",
]
[[package]]
name = "uu_ctrlaltdel"
version = "0.0.1"
@ -1223,9 +1267,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.13.3+wasi-0.2.2"
version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
"wit-bindgen-rt",
]
@ -1374,9 +1418,9 @@ dependencies = [
[[package]]
name = "windows-link"
version = "0.1.0"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
[[package]]
name = "windows-result"
@ -1537,9 +1581,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen-rt"
version = "0.33.0"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags",
]
@ -1551,7 +1595,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e"
dependencies = [
"libc",
"rustix 1.0.0",
"rustix 1.0.3",
]
[[package]]
@ -1562,39 +1606,18 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "zerocopy"
version = "0.7.35"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6"
dependencies = [
"byteorder",
"zerocopy-derive 0.7.35",
]
[[package]]
name = "zerocopy"
version = "0.8.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa91407dacce3a68c56de03abe2760159582b846c6a4acd2f456618087f12713"
dependencies = [
"zerocopy-derive 0.8.17",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626"
checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
dependencies = [
"proc-macro2",
"quote",

@ -27,6 +27,7 @@ uudoc = []
feat_common_core = [
"blockdev",
"chcpu",
"ctrlaltdel",
"dmesg",
"fsfreeze",
@ -60,8 +61,11 @@ serde_json = { version = "1.0.122", features = ["preserve_order"] }
sysinfo = "0.33"
tempfile = "3.9.0"
textwrap = { version = "0.16.0", features = ["terminal_size"] }
thiserror = "2.0"
uucore = "0.0.30"
xattr = "1.3.1"
rangemap = "1.5.1"
syscall-numbers = "4.0.2"
[dependencies]
clap = { workspace = true }
@ -76,6 +80,7 @@ uucore = { workspace = true }
#
blockdev = { optional = true, version = "0.0.1", package = "uu_blockdev", path = "src/uu/blockdev" }
chcpu = { optional = true, version = "0.0.1", package = "uu_chcpu", path = "src/uu/chcpu" }
ctrlaltdel = { optional = true, version = "0.0.1", package = "uu_ctrlaltdel", path = "src/uu/ctrlaltdel" }
dmesg = { optional = true, version = "0.0.1", package = "uu_dmesg", path = "src/uu/dmesg" }
fsfreeze = { optional = true, version = "0.0.1", package = "uu_fsfreeze", path = "src/uu/fsfreeze" }

19
src/uu/chcpu/Cargo.toml Normal file

@ -0,0 +1,19 @@
[package]
name = "uu_chcpu"
version = "0.0.1"
edition = "2024"
[lib]
path = "src/chcpu.rs"
[[bin]]
name = "chcpu"
path = "src/main.rs"
[dependencies]
clap = { workspace = true }
libc = { workspace = true }
rangemap = { workspace = true }
syscall-numbers = { workspace = true }
thiserror = { workspace = true }
uucore = { workspace = true }

11
src/uu/chcpu/chcpu.md Normal file

@ -0,0 +1,11 @@
# chcpu
```
chcpu {-e|--enable|-d|--disable|-c|--configure|-g|--deconfigure} cpu-list
chcpu {-p|--dispatch} mode
chcpu {-r|--rescan}
chcpu {-V|--version}
chcpu {-h|--help}
```
configure CPUs in a multi-processor system.

338
src/uu/chcpu/src/chcpu.rs Normal file

@ -0,0 +1,338 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// Remove this if the tool is ported to Non-UNIX platforms.
#![cfg_attr(not(unix), allow(dead_code))]
mod errors;
#[cfg(unix)]
mod sysfs;
use std::str::FromStr;
use std::{fmt, str};
use clap::builder::{EnumValueParser, PossibleValue};
use clap::{Arg, ArgAction, ArgGroup, Command, ValueEnum, crate_version};
use rangemap::RangeInclusiveSet;
use uucore::{error::UResult, format_usage, help_about, help_usage};
use crate::errors::ChCpuError;
mod options {
pub static ENABLE: &str = "enable";
pub static DISABLE: &str = "disable";
pub static CONFIGURE: &str = "configure";
pub static DECONFIGURE: &str = "deconfigure";
pub static CPU_LIST: &str = "cpu-list";
pub static DISPATCH: &str = "dispatch";
pub static MODE: &str = "mode";
pub static RESCAN: &str = "rescan";
}
const ABOUT: &str = help_about!("chcpu.md");
const USAGE: &str = help_usage!("chcpu.md");
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = uu_app().try_get_matches_from_mut(args)?;
if args.contains_id(options::ENABLE) {
let cpu_list = args
.get_one::<CpuList>(options::ENABLE)
.expect("cpu-list is required");
enable_cpu(cpu_list, true)?;
} else if args.contains_id(options::DISABLE) {
let cpu_list = args
.get_one::<CpuList>(options::DISABLE)
.expect("cpu-list is required");
enable_cpu(cpu_list, false)?;
} else if args.contains_id(options::CONFIGURE) {
let cpu_list = args
.get_one::<CpuList>(options::CONFIGURE)
.expect("cpu-list is required");
configure_cpu(cpu_list, true)?;
} else if args.contains_id(options::DECONFIGURE) {
let cpu_list = args
.get_one::<CpuList>(options::DECONFIGURE)
.expect("cpu-list is required");
configure_cpu(cpu_list, false)?;
} else if args.contains_id(options::DISPATCH) {
let dispatch_mode = args
.get_one::<DispatchMode>(options::DISPATCH)
.expect("mode is required");
set_dispatch_mode(*dispatch_mode)?;
} else if args.get_flag(options::RESCAN) {
rescan_cpus()?;
} else {
unimplemented!();
}
Ok(())
}
impl ValueEnum for DispatchMode {
fn value_variants<'a>() -> &'a [Self] {
&[Self::Horizontal, Self::Vertical]
}
fn to_possible_value<'a>(&self) -> Option<PossibleValue> {
Some(match self {
Self::Horizontal => {
PossibleValue::new("horizontal").help("workload spread across all available CPUs")
}
Self::Vertical => {
PossibleValue::new("vertical").help("workload concentrated on few CPUs")
}
})
}
}
pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.version(crate_version!())
.about(ABOUT)
.override_usage(format_usage(USAGE))
.infer_long_args(true)
.arg_required_else_help(true)
.arg(
Arg::new(options::ENABLE)
.short('e')
.long(options::ENABLE)
.value_name(options::CPU_LIST)
.value_parser(CpuList::from_str)
.action(ArgAction::Set)
.help("enable CPUs"),
)
.arg(
Arg::new(options::DISABLE)
.short('d')
.long(options::DISABLE)
.value_name(options::CPU_LIST)
.value_parser(CpuList::from_str)
.action(ArgAction::Set)
.help("disable CPUs"),
)
.arg(
Arg::new(options::CONFIGURE)
.short('c')
.long(options::CONFIGURE)
.value_name(options::CPU_LIST)
.value_parser(CpuList::from_str)
.action(ArgAction::Set)
.help("configure CPUs"),
)
.arg(
Arg::new(options::DECONFIGURE)
.short('g')
.long(options::DECONFIGURE)
.value_name(options::CPU_LIST)
.value_parser(CpuList::from_str)
.action(ArgAction::Set)
.help("deconfigure CPUs"),
)
.arg(
Arg::new(options::DISPATCH)
.short('p')
.long(options::DISPATCH)
.value_name(options::MODE)
.value_parser(EnumValueParser::<DispatchMode>::new())
.action(ArgAction::Set)
.help("set dispatching mode"),
)
.arg(
Arg::new(options::RESCAN)
.short('r')
.long(options::RESCAN)
.action(ArgAction::SetTrue)
.help("trigger rescan of CPUs"),
)
.group(
ArgGroup::new("control-group")
.args([
options::ENABLE,
options::DISABLE,
options::CONFIGURE,
options::DECONFIGURE,
])
.multiple(false)
.conflicts_with_all(["dispatch-group", "rescan-group"]),
)
.group(
ArgGroup::new("dispatch-group")
.args([options::DISPATCH])
.multiple(false)
.conflicts_with_all(["control-group", "rescan-group"]),
)
.group(
ArgGroup::new("rescan-group")
.args([options::RESCAN])
.multiple(false)
.conflicts_with_all(["control-group", "dispatch-group"]),
)
.after_help(
"<cpu-list> is one or more elements separated by commas. \
Each element is either a positive integer (e.g., 3), \
or an inclusive range of positive integers (e.g., 0-5). \
For example, 0,2,7,10-13 refers to CPUs whose addresses are: 0, 2, 7, 10, 11, 12, and 13.",
)
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
enum DispatchMode {
Horizontal = 0,
Vertical = 1,
}
impl fmt::Display for DispatchMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Horizontal => write!(f, "horizontal"),
Self::Vertical => write!(f, "vertical"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct CpuList(RangeInclusiveSet<usize>);
impl CpuList {
fn run(&self, f: &mut dyn FnMut(usize) -> Result<(), ChCpuError>) -> Result<(), ChCpuError> {
use std::ops::RangeInclusive;
let iter = self.0.iter().flat_map(RangeInclusive::to_owned).map(f);
let (success_occurred, first_error) =
iter.fold((false, None), |(success_occurred, first_error), result| {
if let Err(err) = result {
eprintln!("{err}");
(success_occurred, first_error.or(Some(err)))
} else {
(true, first_error)
}
});
if let Some(err) = first_error {
if success_occurred {
uucore::error::set_exit_code(64); // Partial success.
Ok(())
} else {
Err(err)
}
} else {
Ok(())
}
}
}
impl TryFrom<&[u8]> for CpuList {
type Error = ChCpuError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let set: RangeInclusiveSet<usize> = bytes
.split(|&b| b == b',')
.map(|element| {
// Parsing: ...,element,...
let mut iter = element.splitn(2, |&b| b == b'-').map(<[u8]>::trim_ascii);
let first = iter.next();
(first, iter.next())
})
.map(|(first, last)| {
let first = first.ok_or(ChCpuError::EmptyCpuList)?;
let first: usize = str::from_utf8(first)
.map_err(|_r| ChCpuError::CpuSpecNotPositiveInteger)?
.parse()
.map_err(|_r| ChCpuError::CpuSpecNotPositiveInteger)?;
if let Some(last) = last {
// Parsing: ...,first-last,...
let last = str::from_utf8(last)
.map_err(|_r| ChCpuError::CpuSpecNotPositiveInteger)?
.parse()
.map_err(|_r| ChCpuError::CpuSpecNotPositiveInteger)?;
if first <= last {
Ok(first..=last)
} else {
Err(ChCpuError::CpuSpecFirstAfterLast)
}
} else {
Ok(first..=first) // Parsing: ...,first,...
}
})
.collect::<Result<_, _>>()?;
if set.is_empty() {
Err(ChCpuError::EmptyCpuList)
} else {
Ok(Self(set))
}
}
}
impl FromStr for CpuList {
type Err = ChCpuError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(s.as_bytes())
}
}
#[cfg(unix)]
fn enable_cpu(cpu_list: &CpuList, enable: bool) -> Result<(), ChCpuError> {
let sysfs_cpu = sysfs::SysFSCpu::open()?;
let mut enabled_cpu_list = sysfs_cpu.enabled_cpu_list().ok();
cpu_list.run(&mut move |cpu_index| {
sysfs_cpu.enable_cpu(enabled_cpu_list.as_mut(), cpu_index, enable)
})
}
#[cfg(not(unix))]
fn enable_cpu(_cpu_list: &CpuList, _enable: bool) -> Result<(), ChCpuError> {
unimplemented!()
}
#[cfg(unix)]
fn configure_cpu(cpu_list: &CpuList, configure: bool) -> Result<(), ChCpuError> {
let sysfs_cpu = sysfs::SysFSCpu::open()?;
let enabled_cpu_list = sysfs_cpu.enabled_cpu_list().ok();
cpu_list.run(&mut move |cpu_index| {
sysfs_cpu.configure_cpu(enabled_cpu_list.as_ref(), cpu_index, configure)
})
}
#[cfg(not(unix))]
fn configure_cpu(_cpu_list: &CpuList, _configure: bool) -> Result<(), ChCpuError> {
unimplemented!()
}
#[cfg(unix)]
fn set_dispatch_mode(dispatch_mode: DispatchMode) -> Result<(), ChCpuError> {
sysfs::SysFSCpu::open()?.set_dispatch_mode(dispatch_mode)
}
#[cfg(not(unix))]
fn set_dispatch_mode(_dispatch_mode: DispatchMode) -> Result<(), ChCpuError> {
unimplemented!()
}
#[cfg(unix)]
fn rescan_cpus() -> Result<(), ChCpuError> {
sysfs::SysFSCpu::open()?.rescan_cpus()
}
#[cfg(not(unix))]
fn rescan_cpus() -> Result<(), ChCpuError> {
unimplemented!()
}

@ -0,0 +1,92 @@
// This file is part of the uutils hostname package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::path::PathBuf;
#[derive(Debug, thiserror::Error)]
pub enum ChCpuError {
#[error("CPU {0} is enabled")]
CpuIsEnabled(usize),
#[error("CPU {0} is not configurable")]
CpuNotConfigurable(usize),
#[error("CPU {0} is not hot pluggable")]
CpuNotHotPluggable(usize),
#[error("this system does not support rescanning of CPUs")]
CpuRescanUnsupported,
#[error("first element of CPU list range is greater than its last element")]
CpuSpecFirstAfterLast,
#[error("CPU list element is not a positive number")]
CpuSpecNotPositiveInteger,
#[error("CPU list is empty")]
EmptyCpuList,
#[error("CPU {0} does not exist")]
InvalidCpuIndex(usize),
#[error("{0}: {1}")]
IO0(String, std::io::Error),
#[error("{0} '{path}': {2}", path = .1.display())]
IO1(String, PathBuf, std::io::Error),
#[error("only one CPU is enabled")]
OneCpuIsEnabled,
#[error("data is not an integer '{0}'")]
NotInteger(String),
#[error("this system does not support setting the dispatching mode of CPUs")]
SetCpuDispatchUnsupported,
}
impl ChCpuError {
pub(crate) fn io0(message: impl Into<String>, error: std::io::Error) -> Self {
Self::IO0(message.into(), error)
}
pub(crate) fn io1(
message: impl Into<String>,
path: impl Into<PathBuf>,
error: std::io::Error,
) -> Self {
Self::IO1(message.into(), path.into(), error)
}
pub(crate) fn with_io_message(self, message: impl Into<String>) -> Self {
match self {
Self::IO0(_, err) => Self::IO0(message.into(), err),
Self::IO1(_, path, err) => Self::IO1(message.into(), path, err),
Self::CpuIsEnabled(_)
| Self::CpuNotConfigurable(_)
| Self::CpuNotHotPluggable(_)
| Self::CpuRescanUnsupported
| Self::CpuSpecFirstAfterLast
| Self::CpuSpecNotPositiveInteger
| Self::EmptyCpuList
| Self::InvalidCpuIndex(_)
| Self::OneCpuIsEnabled
| Self::NotInteger(_)
| Self::SetCpuDispatchUnsupported => self,
}
}
}
impl uucore::error::UError for ChCpuError {
fn code(&self) -> i32 {
1
}
fn usage(&self) -> bool {
false
}
}

1
src/uu/chcpu/src/main.rs Normal file

@ -0,0 +1 @@
uucore::bin!(uu_chcpu);

270
src/uu/chcpu/src/sysfs.rs Normal file

@ -0,0 +1,270 @@
// This file is part of the uutils hostname package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::ffi::{CString, c_int};
use std::fs::{File, OpenOptions};
use std::io::{BufRead, BufReader, Read, Write, stdout};
use std::os::fd::{AsRawFd, FromRawFd};
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::OpenOptionsExt;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::{fmt, str};
use crate::errors::ChCpuError;
use crate::{CpuList, DispatchMode};
pub(crate) const PATH_SYS_CPU: &str = "/sys/devices/system/cpu";
pub(crate) struct SysFSCpu(File);
impl SysFSCpu {
pub(crate) fn open() -> Result<Self, ChCpuError> {
OpenOptions::new()
.read(true)
.custom_flags(libc::O_CLOEXEC)
.open(PATH_SYS_CPU)
.map(Self)
.map_err(|err| ChCpuError::io1("failed to open", PATH_SYS_CPU, err))
}
fn inner_path(name: impl AsRef<Path>) -> PathBuf {
Path::new(PATH_SYS_CPU).join(name)
}
pub(crate) fn ensure_accessible(
&self,
name: impl AsRef<Path>,
access: c_int,
) -> Result<(), ChCpuError> {
use std::io::Error;
let name = name.as_ref();
let c_name = c_string_from_path(name)?;
if unsafe { libc::faccessat(self.0.as_raw_fd(), c_name.as_ptr(), access, 0) } == 0 {
Ok(())
} else {
let path = Self::inner_path(name);
let err = Error::last_os_error();
Err(ChCpuError::io1("file/directory is inaccessible", path, err))
}
}
pub(crate) fn open_inner(
&self,
name: impl AsRef<Path>,
flags: c_int,
) -> Result<File, ChCpuError> {
use std::io::Error;
let name = name.as_ref();
let c_name = c_string_from_path(name)?;
unsafe {
let fd = libc::openat(self.0.as_raw_fd(), c_name.as_ptr(), flags);
if fd >= 0 {
return Ok(File::from_raw_fd(fd));
}
}
let path = Self::inner_path(name);
let err = Error::last_os_error();
Err(ChCpuError::io1("failed to open", path, err))
}
pub(crate) fn read_value<T>(&self, name: impl AsRef<Path>) -> Result<T, ChCpuError>
where
T: FromStr,
{
let name = name.as_ref();
let mut line = String::default();
self.open_inner(name, libc::O_RDONLY | libc::O_CLOEXEC)
.map(BufReader::new)?
.read_line(&mut line)
.map_err(|err| ChCpuError::io1("failed to read file", Self::inner_path(name), err))?;
line.trim()
.parse()
.map_err(|_r| ChCpuError::NotInteger(line.trim().into()))
}
pub(crate) fn write_value(
&self,
name: impl AsRef<Path>,
value: impl fmt::Display,
) -> Result<(), ChCpuError> {
let name = name.as_ref();
self.open_inner(name, libc::O_WRONLY | libc::O_CLOEXEC)?
.write_all(format!("{value}").as_bytes())
.map_err(|err| ChCpuError::io1("failed to write file", Self::inner_path(name), err))
}
pub(crate) fn enabled_cpu_list(&self) -> Result<CpuList, ChCpuError> {
let mut buffer = Vec::default();
self.open_inner("online", libc::O_RDONLY | libc::O_CLOEXEC)?
.read_to_end(&mut buffer)
.map_err(|err| {
ChCpuError::io1("failed to read file", Self::inner_path("online"), err)
})?;
CpuList::try_from(buffer.as_slice())
}
pub(crate) fn cpu_dir_path(&self, cpu_index: usize) -> Result<PathBuf, ChCpuError> {
let dir_name = PathBuf::from(format!("cpu{cpu_index}"));
self.ensure_accessible(&dir_name, libc::F_OK)
.map(|()| dir_name)
.map_err(|_r| ChCpuError::InvalidCpuIndex(cpu_index))
}
pub(crate) fn enable_cpu(
&self,
enabled_cpu_list: Option<&mut CpuList>,
cpu_index: usize,
enable: bool,
) -> Result<(), ChCpuError> {
use std::ops::RangeInclusive;
let dir_name = self.cpu_dir_path(cpu_index)?;
let online_path = dir_name.join("online");
self.ensure_accessible(&online_path, libc::F_OK)
.map_err(|_r| ChCpuError::CpuNotHotPluggable(cpu_index))?;
let online = self
.read_value::<i32>(&online_path)
.map(|value| value != 0)?;
let new_state = if enable { "enabled" } else { "disabled" };
if enable == online {
let mut stdout = stdout().lock();
return writeln!(&mut stdout, "CPU {cpu_index} is already {new_state}")
.map_err(|err| ChCpuError::io0("write standard output", err));
}
if let Some(enabled_cpu_list) = &enabled_cpu_list {
let iter = enabled_cpu_list
.0
.iter()
.flat_map(RangeInclusive::to_owned)
.take(2);
if !enable && iter.count() <= 1 {
return Err(ChCpuError::OneCpuIsEnabled);
}
}
let configured = self.read_value::<i32>(dir_name.join("configure"));
if let Err(err) = self.write_value(&online_path, u8::from(enable)) {
let operation = if enable { "enable" } else { "disable" };
let reason = if enable && configured.is_ok_and(|value| value == 0) {
" (CPU is deconfigured)"
} else {
""
};
return Err(err.with_io_message(format!("CPU {cpu_index} {operation} failed{reason}")));
}
if let Some(enabled_cpu_list) = enabled_cpu_list {
if enable {
enabled_cpu_list.0.insert(cpu_index..=cpu_index);
} else {
enabled_cpu_list.0.remove(cpu_index..=cpu_index);
}
}
let mut stdout = stdout().lock();
writeln!(&mut stdout, "CPU {cpu_index} {new_state}",)
.map_err(|err| ChCpuError::io0("write standard output", err))
}
pub(crate) fn configure_cpu(
&self,
enabled_cpu_list: Option<&CpuList>,
cpu_index: usize,
configure: bool,
) -> Result<(), ChCpuError> {
let dir_name = self.cpu_dir_path(cpu_index)?;
let configure_path = dir_name.join("configure");
self.ensure_accessible(&configure_path, libc::F_OK)
.map_err(|_r| ChCpuError::CpuNotConfigurable(cpu_index))?;
let previous_config = self
.read_value::<i32>(&configure_path)
.map(|value| value != 0)?;
let new_state = if configure {
"configured"
} else {
"deconfigured"
};
if configure == previous_config {
let mut stdout = stdout().lock();
return writeln!(&mut stdout, "CPU {cpu_index} is already {new_state}")
.map_err(|err| ChCpuError::io0("write standard output", err));
}
if let Some(enabled_cpu_list) = enabled_cpu_list {
if previous_config && !configure && enabled_cpu_list.0.contains(&cpu_index) {
return Err(ChCpuError::CpuIsEnabled(cpu_index));
}
}
if let Err(err) = self.write_value(&configure_path, u8::from(configure)) {
let operation = if configure {
"configure"
} else {
"deconfigure"
};
Err(err.with_io_message(format!("CPU {cpu_index} {operation} failed")))
} else {
let mut stdout = stdout().lock();
writeln!(&mut stdout, "CPU {cpu_index} {new_state}",)
.map_err(|err| ChCpuError::io0("write standard output", err))
}
}
pub(crate) fn set_dispatch_mode(&self, mode: DispatchMode) -> Result<(), ChCpuError> {
self.ensure_accessible("dispatching", libc::F_OK)
.map_err(|_r| ChCpuError::SetCpuDispatchUnsupported)?;
self.write_value("dispatching", mode as u8)
.map_err(|err| err.with_io_message("failed to set dispatch mode"))?;
let mut stdout = stdout().lock();
writeln!(&mut stdout, "Successfully set {mode} dispatching mode")
.map_err(|err| ChCpuError::io0("write standard output", err))
}
pub(crate) fn rescan_cpus(&self) -> Result<(), ChCpuError> {
self.ensure_accessible("rescan", libc::F_OK)
.map_err(|_r| ChCpuError::CpuRescanUnsupported)?;
self.write_value("rescan", "1")
.map_err(|err| err.with_io_message("failed to trigger rescan of CPUs"))?;
let mut stdout = stdout().lock();
writeln!(&mut stdout, "Triggered rescan of CPUs")
.map_err(|err| ChCpuError::io0("write standard output", err))
}
}
fn c_string_from_path(path: &Path) -> Result<CString, ChCpuError> {
use std::io::{Error, ErrorKind};
CString::new(path.as_os_str().as_bytes())
.map_err(|_r| ChCpuError::io1("invalid name", path, Error::from(ErrorKind::InvalidInput)))
}