Implemented chcpu
.
This commit is contained in:
parent
b66945dee8
commit
31795f2729
4
.gitignore
vendored
4
.gitignore
vendored
@ -1 +1,3 @@
|
||||
/target
|
||||
syntax: glob
|
||||
|
||||
/target/
|
||||
|
29
Cargo.lock
generated
29
Cargo.lock
generated
@ -888,6 +888,12 @@ dependencies = [
|
||||
"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"
|
||||
@ -937,6 +943,26 @@ dependencies = [
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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"
|
||||
@ -1062,7 +1088,10 @@ name = "uu_chcpu"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"libc",
|
||||
"rangemap",
|
||||
"syscall-numbers",
|
||||
"thiserror",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
|
@ -61,9 +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 }
|
||||
|
@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "uu_chcpu"
|
||||
name = "uu_chcpu"
|
||||
version = "0.0.1"
|
||||
edition = "2024"
|
||||
|
||||
@ -11,6 +11,9 @@ name = "chcpu"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
uucore = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
rangemap = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
libc = { workspace = true }
|
||||
rangemap = { workspace = true }
|
||||
syscall-numbers = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
uucore = { workspace = true }
|
||||
|
@ -3,9 +3,15 @@
|
||||
// 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};
|
||||
@ -28,10 +34,47 @@ mod options {
|
||||
const ABOUT: &str = help_about!("chcpu.md");
|
||||
const USAGE: &str = help_usage!("chcpu.md");
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum DispatchMode {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
#[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 {
|
||||
@ -51,121 +94,6 @@ impl ValueEnum for DispatchMode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct CpuList(RangeInclusiveSet<usize>);
|
||||
|
||||
impl FromStr for CpuList {
|
||||
type Err = ChCpuError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let set: RangeInclusiveSet<usize> = s
|
||||
.split(',')
|
||||
.map(|element| {
|
||||
// Parsing: ...,element,...
|
||||
let mut iter = element.splitn(2, '-').map(str::trim);
|
||||
let first = iter.next();
|
||||
(first, iter.next())
|
||||
})
|
||||
.map(|(first, last)| {
|
||||
let first: usize = first
|
||||
.ok_or(ChCpuError::EmptyCpuList)?
|
||||
.parse()
|
||||
.map_err(|_r| ChCpuError::CpuSpecNotPositiveInteger)?;
|
||||
|
||||
if let Some(last) = last {
|
||||
// Parsing: ...,first-last,...
|
||||
let last =
|
||||
str::parse(last).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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn enable_cpu_list(_cpu_list: &CpuList) -> UResult<()> {
|
||||
dbg!(_cpu_list);
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn disable_cpu_list(_cpu_list: &CpuList) -> UResult<()> {
|
||||
dbg!(_cpu_list);
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn configure_cpu_list(_cpu_list: &CpuList) -> UResult<()> {
|
||||
dbg!(_cpu_list);
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn deconfigure_cpu_list(_cpu_list: &CpuList) -> UResult<()> {
|
||||
dbg!(_cpu_list);
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn set_dispatch_mode(_mode: DispatchMode) -> UResult<()> {
|
||||
dbg!(_mode);
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn rescan_cpus() -> UResult<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[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_list(cpu_list)
|
||||
} else if args.contains_id(options::DISABLE) {
|
||||
let cpu_list = args
|
||||
.get_one::<CpuList>(options::DISABLE)
|
||||
.expect("cpu-list is required");
|
||||
|
||||
disable_cpu_list(cpu_list)
|
||||
} else if args.contains_id(options::CONFIGURE) {
|
||||
let cpu_list = args
|
||||
.get_one::<CpuList>(options::CONFIGURE)
|
||||
.expect("cpu-list is required");
|
||||
|
||||
configure_cpu_list(cpu_list)
|
||||
} else if args.contains_id(options::DECONFIGURE) {
|
||||
let cpu_list = args
|
||||
.get_one::<CpuList>(options::DECONFIGURE)
|
||||
.expect("cpu-list is required");
|
||||
|
||||
deconfigure_cpu_list(cpu_list)
|
||||
} 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!();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uu_app() -> Command {
|
||||
Command::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
@ -255,3 +183,156 @@ pub fn uu_app() -> Command {
|
||||
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!()
|
||||
}
|
||||
|
@ -3,35 +3,85 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use uucore::error::UError;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ChCpuError {
|
||||
EmptyCpuList,
|
||||
CpuSpecNotPositiveInteger,
|
||||
#[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 fmt::Display for ChCpuError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
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::EmptyCpuList => write!(f, "CPU list is empty"),
|
||||
Self::CpuSpecNotPositiveInteger => {
|
||||
write!(f, "CPU list element is not a positive number")
|
||||
}
|
||||
Self::CpuSpecFirstAfterLast => {
|
||||
write!(
|
||||
f,
|
||||
"first element of CPU list range is greater than its last element"
|
||||
)
|
||||
}
|
||||
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 UError for ChCpuError {
|
||||
impl uucore::error::UError for ChCpuError {
|
||||
fn code(&self) -> i32 {
|
||||
1
|
||||
}
|
||||
@ -40,5 +90,3 @@ impl UError for ChCpuError {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ChCpuError {}
|
||||
|
270
src/uu/chcpu/src/sysfs.rs
Normal file
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)))
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user