diff --git a/.gitignore b/.gitignore
index ea8c4bf..5b3bd55 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
-/target
+syntax: glob
+
+/target/
diff --git a/Cargo.lock b/Cargo.lock
index 8a41289..8310026 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/Cargo.toml b/Cargo.toml
index 5bad5d9..cd53738 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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" }
diff --git a/src/uu/chcpu/Cargo.toml b/src/uu/chcpu/Cargo.toml
new file mode 100644
index 0000000..9599c06
--- /dev/null
+++ b/src/uu/chcpu/Cargo.toml
@@ -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 }
diff --git a/src/uu/chcpu/chcpu.md b/src/uu/chcpu/chcpu.md
new file mode 100644
index 0000000..14d273a
--- /dev/null
+++ b/src/uu/chcpu/chcpu.md
@@ -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.
diff --git a/src/uu/chcpu/src/chcpu.rs b/src/uu/chcpu/src/chcpu.rs
new file mode 100644
index 0000000..810f5a8
--- /dev/null
+++ b/src/uu/chcpu/src/chcpu.rs
@@ -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!()
+}
diff --git a/src/uu/chcpu/src/errors.rs b/src/uu/chcpu/src/errors.rs
new file mode 100644
index 0000000..d26e753
--- /dev/null
+++ b/src/uu/chcpu/src/errors.rs
@@ -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
+    }
+}
diff --git a/src/uu/chcpu/src/main.rs b/src/uu/chcpu/src/main.rs
new file mode 100644
index 0000000..fe72ddc
--- /dev/null
+++ b/src/uu/chcpu/src/main.rs
@@ -0,0 +1 @@
+uucore::bin!(uu_chcpu);
diff --git a/src/uu/chcpu/src/sysfs.rs b/src/uu/chcpu/src/sysfs.rs
new file mode 100644
index 0000000..e06a268
--- /dev/null
+++ b/src/uu/chcpu/src/sysfs.rs
@@ -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)))
+}