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 246399e..8310026 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index 402688a..cd53738 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } diff --git a/src/uu/chcpu/Cargo.toml b/src/uu/chcpu/Cargo.toml index afaefed..9599c06 100644 --- a/src/uu/chcpu/Cargo.toml +++ b/src/uu/chcpu/Cargo.toml @@ -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 } diff --git a/src/uu/chcpu/src/chcpu.rs b/src/uu/chcpu/src/chcpu.rs index 803802e..810f5a8 100644 --- a/src/uu/chcpu/src/chcpu.rs +++ b/src/uu/chcpu/src/chcpu.rs @@ -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::(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::(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::(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::(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::(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); - -impl FromStr for CpuList { - type Err = ChCpuError; - - fn from_str(s: &str) -> Result { - let set: RangeInclusiveSet = 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::>()?; - - 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::(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::(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::(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::(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::(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); + +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 { + let set: RangeInclusiveSet = 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::>()?; + + 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::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 index c3a8fdf..d26e753 100644 --- a/src/uu/chcpu/src/errors.rs +++ b/src/uu/chcpu/src/errors.rs @@ -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, error: std::io::Error) -> Self { + Self::IO0(message.into(), error) + } + + pub(crate) fn io1( + message: impl Into, + path: impl Into, + error: std::io::Error, + ) -> Self { + Self::IO1(message.into(), path.into(), error) + } + + pub(crate) fn with_io_message(self, message: impl Into) -> 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 {} 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 { + 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) -> PathBuf { + Path::new(PATH_SYS_CPU).join(name) + } + + pub(crate) fn ensure_accessible( + &self, + name: impl AsRef, + 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, + flags: c_int, + ) -> Result { + 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(&self, name: impl AsRef) -> Result + 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, + 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 { + 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 { + 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::(&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::(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::(&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 { + 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))) +}