From 16f32e7e9da436bc5a7a6c7366202acefabaa52e Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Sat, 5 Aug 2023 22:52:35 -0400 Subject: [PATCH] Improve API ergonomics and add optional additional derives --- Cargo.toml | 4 +++ src/attribute.rs | 17 ++++++++++--- src/basic.rs | 3 ++- src/capability.rs | 53 +++++++++++++++++++++++++++++++++++++++ src/entity.rs | 61 ++++++++++++++++++++++++++++++--------------- src/errors.rs | 4 +-- src/hwmon.rs | 16 +++++++++++- src/lib.rs | 2 ++ src/power_supply.rs | 25 ++++++++++++++++--- src/syspath.rs | 57 ++++++++++++++++++++++++++++++++++++++---- 10 files changed, 207 insertions(+), 35 deletions(-) create mode 100644 src/capability.rs diff --git a/Cargo.toml b/Cargo.toml index 35bf9b9..92256fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] + +[features] +default = [] +derive = [] diff --git a/src/attribute.rs b/src/attribute.rs index 405049e..a1967db 100644 --- a/src/attribute.rs +++ b/src/attribute.rs @@ -5,7 +5,7 @@ use std::str::FromStr; use crate::EitherErr2; /// sysfs entity attribute file functionality -pub trait SysAttribute: Eq { +pub trait SysAttribute { /// attribute file name fn filename(&self) -> PathBuf; @@ -29,6 +29,11 @@ pub trait SysAttribute: Eq { fn write_value(&self, entity: &dyn crate::SysEntity, value: &[u8]) -> IoResult<()> { std::fs::write(self.path(entity), value) } + + /// Returns true if the attribute is readonly + fn readonly(&self, entity: &dyn crate::SysEntity) -> bool { + self.path(entity).metadata().map(|meta| meta.permissions().readonly()).unwrap_or(false) + } } impl SysAttribute for String { @@ -37,8 +42,14 @@ impl SysAttribute for String { } } +impl SysAttribute for &str { + fn filename(&self) -> PathBuf { + PathBuf::from(self) + } +} + /// sysfs entity attribute functionality extension -pub trait SysAttributeExt: SysAttribute { +pub trait SysAttributeExt: SysAttribute + Eq { /// read attribute as string fn read_str(&self, entity: &dyn crate::SysEntity) -> IoResult { std::fs::read_to_string(self.path(entity)) @@ -56,4 +67,4 @@ pub trait SysAttributeExt: SysAttribute { } } -impl SysAttributeExt for X {} +impl SysAttributeExt for X {} diff --git a/src/basic.rs b/src/basic.rs index 375c095..2174f51 100644 --- a/src/basic.rs +++ b/src/basic.rs @@ -5,6 +5,7 @@ use std::io::Result as IoResult; const SYS_CLASS_PATH: &str = "sys/class"; /// Generic entity path with basic functionality +#[cfg_attr(feature = "derive", derive(Debug, Clone))] pub struct BasicEntityPath { path: PathBuf } @@ -61,7 +62,7 @@ impl crate::SysEntity for BasicEntityPath { } } -impl crate::SysEntityAttributes::Err> for BasicEntityPath { +impl crate::SysEntityAttributes for BasicEntityPath { fn capabilities(&self) -> Vec { if let Ok(dir_iter) = self.path.read_dir() { dir_iter.filter_map( diff --git a/src/capability.rs b/src/capability.rs new file mode 100644 index 0000000..4dc74d1 --- /dev/null +++ b/src/capability.rs @@ -0,0 +1,53 @@ +//! sysfs class entity capability filtering + +/// Capabilities filter +pub trait Capabilities> { + /// Are the required capabilities (self) satisfied by the provided capabilities (the parameter)? + fn can(&mut self, capabilities: &Vec) -> bool; +} + +impl ) -> bool, A: crate::SysAttribute, R: std::borrow::Borrow> Capabilities for F { + fn can(&mut self, capabilities: &Vec) -> bool { + (self)(capabilities) + } +} + +/// Build a Capabilities implementor which needs specific attributes to exist +pub fn attributes<'a, A: crate::SysAttribute + Eq + 'a, R: std::borrow::Borrow + 'a>(attributes: impl Iterator) -> impl Capabilities + 'a { + AllNeedsCapabilities::new( + attributes.map(|attr: R| { move |other_attr: &R| attr.borrow() == other_attr.borrow() }).collect() + ) +} + + +struct AllNeedsCapabilities<'a, 'f, A: crate::SysAttribute + 'a, R: std::borrow::Borrow + 'a, F: (FnMut(&R) -> bool) + 'f> { + wanted: Vec, + _attr_ty: std::marker::PhantomData, + _attr_brw_ty: std::marker::PhantomData, + _life_attr_ty: std::marker::PhantomData<&'a ()>, + _life_fn_ty: std::marker::PhantomData<&'f ()>, +} + +impl <'a, 'f, A: crate::SysAttribute + 'a, R: std::borrow::Borrow + 'a, F: (FnMut(&R) -> bool) + 'f> AllNeedsCapabilities<'a, 'f, A, R, F> { + pub(crate) fn new(wanted: Vec) -> Self { + Self { + wanted, + _attr_ty: Default::default(), + _attr_brw_ty: Default::default(), + _life_attr_ty: Default::default(), + _life_fn_ty: Default::default(), + } + } +} + +impl <'a, 'f, A: crate::SysAttribute + 'a, R: std::borrow::Borrow + 'a, F: (FnMut(&R) -> bool) + 'f> Capabilities for AllNeedsCapabilities<'a, 'f, A, R, F> { + fn can(&mut self, capabilities: &Vec) -> bool { + let mut wants = vec![false; self.wanted.len()]; + for capability in capabilities { + if let Some(pos) = self.wanted.iter_mut().position(|w| w(capability)) { + wants[pos] = true; + } + } + wants.into_iter().all(|item| item) + } +} diff --git a/src/entity.rs b/src/entity.rs index ee21e9c..40f3041 100644 --- a/src/entity.rs +++ b/src/entity.rs @@ -6,18 +6,39 @@ use crate::EitherErr2; use crate::SysAttributeExt; /// sysfs class entity functionality -pub trait SysEntity: AsRef { +#[cfg(feature = "derive")] +pub trait SysEntity: AsRef + core::fmt::Debug { /// Convert specialized entity into general entity path container fn to_entity_path(self) -> EntityPath; /// Get the entity's name fn name(&self) -> IoResult; + + /// Try to get the entity's root sysfs dir (probably `/`) + fn root(&self) -> Option { + crate::SysPath::from_entity_path(self) + } +} + +/// sysfs class entity functionality +#[cfg(not(feature = "derive"))] +pub trait SysEntity: AsRef { + /// Convert specialized entity into general entity path container + fn to_entity_path(self) -> EntityPath; + + /// Get the entity's name + fn name(&self) -> IoResult; + + /// Try to get the entity's root sysfs dir (probably `/`) + fn root(&self) -> Option { + crate::SysPath::from_entity_path(self) + } } /// sysfs class entity functionality extension pub trait SysEntityRawExt: SysEntity { /// Get an attribute on the entity - fn attribute, E>(&self, attr: A) -> Result>; + fn attribute, E>(&self, attr: A) -> Result>; /// Get an attribute by filename in the entity's directory fn attribute_str>(&self, attr: A) -> IoResult; @@ -29,7 +50,7 @@ pub trait SysEntityRawExt: SysEntity { } impl SysEntityRawExt for X { - fn attribute, E>(&self, attr: A) -> Result> { + fn attribute, E>(&self, attr: A) -> Result> { attr.parse(self) } @@ -48,21 +69,26 @@ impl SysEntityRawExt for X { /// sysfs class entity attribute type indicator -pub trait SysEntityAttributes, E>: SysEntity + Sized { - /// Get an attribute on the entity - fn attribute(&self, attr: A) -> Result> { - attr.parse(self) - } - +pub trait SysEntityAttributes: SysEntity + Sized { /// Get attributes available on this entity; fn capabilities(&self) -> Vec; } -/*pub trait SysEntityAttributesExt, E>: SysEntityAttributes { - fn attribute() -}*/ +pub trait SysEntityAttributesExt: SysEntityAttributes { + fn capable>(&self, mut capabilities: C) -> bool { + capabilities.can(&self.capabilities()) + } + + /// Get an attribute on the entity + fn attribute, E>(&self, attr: A) -> Result> { + attr.parse(self) + } +} + +impl > SysEntityAttributesExt for X {} /// sysfs class entity implementors +#[cfg_attr(feature = "derive", derive(Debug, Clone))] pub enum EntityPath { /// hwmon HwMon(crate::HwMonPath), @@ -71,7 +97,7 @@ pub enum EntityPath { /// Generic Generic(crate::BasicEntityPath), /// Miscellaneous - Custom(Box), + Custom(std::sync::Arc>), } impl EntityPath { @@ -80,19 +106,14 @@ impl EntityPath { Self::HwMon(inner) => inner, Self::PowerSupply(inner) => inner, Self::Generic(inner) => inner, - Self::Custom(inner) => inner.as_ref(), + Self::Custom(inner) => inner.as_ref().as_ref(), } } } impl AsRef for EntityPath { fn as_ref(&self) -> &Path { - match self { - Self::HwMon(inner) => inner.as_ref(), - Self::PowerSupply(inner) => inner.as_ref(), - Self::Generic(inner) => inner.as_ref(), - Self::Custom(inner) => inner.as_ref().as_ref(), - } + self.inner().as_ref() } } diff --git a/src/errors.rs b/src/errors.rs index cb2265b..b641af2 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,5 +1,5 @@ /// Multi-error convenience enum -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum EitherErr2 { /// Type 1 First(T1), @@ -32,7 +32,7 @@ impl std::error::Error for Either /// Value parse error for an enumeration. /// This usually indicates an unexpected value (one without an enum variant) was received while parsing. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ValueEnumError<'a> { pub(crate) got: String, pub(crate) allowed: &'a [&'a str], diff --git a/src/hwmon.rs b/src/hwmon.rs index a0911c8..faed7a2 100644 --- a/src/hwmon.rs +++ b/src/hwmon.rs @@ -7,11 +7,13 @@ const HWMON_DIR_PATH: &'static str = "sys/class/hwmon/"; /// Attribute representation. /// Attribute files are usually in the format `_`. #[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "derive", derive(Debug))] pub struct HwMonAttribute { inner: HwMonAttributeInner, } #[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "derive", derive(Debug))] enum HwMonAttributeInner { Name, Standard { @@ -20,6 +22,7 @@ enum HwMonAttributeInner { item: HwMonAttributeItem, }, Uevent, + Custom(&'static str), } impl std::str::FromStr for HwMonAttributeInner { @@ -46,6 +49,7 @@ impl std::str::FromStr for HwMonAttributeInner { /// Attribute type in the format `_` #[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "derive", derive(Debug))] pub enum HwMonAttributeType { /// Voltage In, @@ -84,6 +88,7 @@ impl HwMonAttributeType { /// Attribute item in the format `_` #[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "derive", derive(Debug))] pub enum HwMonAttributeItem { /// Input Input, @@ -136,12 +141,20 @@ impl HwMonAttribute { inner: HwMonAttributeInner::Uevent } } + + /// Custom entity attribute + pub const fn custom(attr: &'static str) -> Self { + Self { + inner: HwMonAttributeInner::Custom(attr) + } + } fn to_attr_str(&self) -> String { match self.inner { HwMonAttributeInner::Name => "name".to_string(), HwMonAttributeInner::Standard { ty, number, item } => format!("{}{}_{}", ty.to_attr_str(), number, item.to_attr_str()), HwMonAttributeInner::Uevent => "uevent".to_string(), + HwMonAttributeInner::Custom(s) => s.to_owned(), } } } @@ -153,6 +166,7 @@ impl crate::SysAttribute for HwMonAttribute { } /// hwmon/ directory +#[cfg_attr(feature = "derive", derive(Debug, Clone))] pub struct HwMonPath { path: PathBuf } @@ -226,7 +240,7 @@ impl crate::SysEntity for HwMonPath { } } -impl crate::SysEntityAttributes::Err> for HwMonPath { +impl crate::SysEntityAttributes for HwMonPath { fn capabilities(&self) -> Vec { if let Ok(dir_iter) = self.path.read_dir() { dir_iter.filter_map( diff --git a/src/lib.rs b/src/lib.rs index 1acc06a..8384d6f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,8 @@ pub use attribute::{SysAttribute, SysAttributeExt}; mod basic; pub use basic::BasicEntityPath; +pub mod capability; + mod entity; pub use entity::{EntityPath, SysEntity, SysEntityRawExt, SysEntityAttributes}; diff --git a/src/power_supply.rs b/src/power_supply.rs index 86a3ff2..6e39cb1 100644 --- a/src/power_supply.rs +++ b/src/power_supply.rs @@ -11,7 +11,8 @@ const CLASS_PATH: &'static str = "power_supply"; /// Attribute files in a power supply directory /// These attributes are taken from https://github.com/torvalds/linux/blob/master/Documentation/ABI/testing/sysfs-class-power -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "derive", derive(Debug))] pub enum PowerSupplyAttribute { /// Reports the name of the device manufacturer. Manufacturer, @@ -65,6 +66,12 @@ pub enum PowerSupplyAttribute { ChargeType, /// Reports the charging current value which is used to determine when the battery is considered full and charging should end (uA). ChargeTermCurrent, + /// Reports the instantaneous charge value of the power supply (uA). + ChargeNow, + /// Reports the maximum charge value of the power supply (uA). + ChargeFull, + /// Reports the maximum rated charge value of the power supply (uA). + ChargeFullDesign, /// Reports the health of the battery or battery side of charger functionality ( "Unknown", "Good", "Overheat", "Dead", "Over voltage", "Unspecified failure", "Cold", "Watchdog timer expire", "Safety timer expire", "Over current", "Calibration required", "Warm", "Cool", "Hot", or "No battery"). Health, /// Reports the charging current applied during pre-charging phase for a battery charge cycle (uA). @@ -82,6 +89,8 @@ pub enum PowerSupplyAttribute { /// Reports the number of full charge + discharge cycles the battery has undergone. CycleCount, // TODO USB properties + /// Custom entity attribute + Custom(&'static str) } impl crate::SysAttribute for PowerSupplyAttribute { @@ -113,6 +122,9 @@ impl crate::SysAttribute for PowerSupplyAttribute { Self::ChargeControlEndThreshold => "charge_control_end_threshold", Self::ChargeType => "charge_type", Self::ChargeTermCurrent => "charge_term_current", + Self::ChargeNow => "charge_now", + Self::ChargeFull => "charge_full", + Self::ChargeFullDesign => "charge_full_design", Self::Health => "health", Self::PrechargeCurrent => "precharge_current", Self::Present => "present", @@ -121,13 +133,15 @@ impl crate::SysAttribute for PowerSupplyAttribute { Self::Technology => "technology", Self::VoltageAverage => "voltage_avg", Self::CycleCount => "cycle_count", + Self::Custom(s) => s, }; s.into() } } /// power_supply//type attribute value -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "derive", derive(Debug))] pub enum PowerSupplyType { /// Battery Battery, @@ -184,6 +198,7 @@ impl std::str::FromStr for PowerSupplyType { } /// power_supply// directory +#[cfg_attr(feature = "derive", derive(Debug, Clone))] pub struct PowerSupplyPath { inner: crate::BasicEntityPath, } @@ -229,7 +244,7 @@ impl crate::SysEntity for PowerSupplyPath { } } -impl crate::SysEntityAttributes::Err> for PowerSupplyPath { +impl crate::SysEntityAttributes for PowerSupplyPath { fn capabilities(&self) -> Vec { self.inner.filter_capabilities(vec![ PowerSupplyAttribute::Manufacturer, @@ -278,6 +293,10 @@ mod tests { #[test] fn power_supply_all() -> std::io::Result<()> { + if std::fs::read_dir("/sys/class/power_supply")?.count() == 0 { + eprintln!("power_supply test skipped since none exist (maybe running on a desktop PC?)"); + return Ok(()) + } let sys = crate::SysPath::default(); let all_psu: Vec<_> = PowerSupplyPath::all(sys)?.collect(); assert!(!all_psu.is_empty()); diff --git a/src/syspath.rs b/src/syspath.rs index c0dd0e9..b069dae 100644 --- a/src/syspath.rs +++ b/src/syspath.rs @@ -1,9 +1,10 @@ use std::path::{Path, PathBuf}; use std::convert::AsRef; -use crate::SysEntity; +use crate::{SysEntity, SysEntityAttributes}; /// sysfs root +#[cfg_attr(feature = "derive", derive(Debug))] pub struct SysPath { root: PathBuf, } @@ -35,16 +36,42 @@ impl SysPath { } /// Find a hardware monitor entry by index - pub fn hwmon_by_index(&self, index: u64) -> std::io::Result { + pub fn hwmon_by_index(&self, index: u64) -> crate::HwMonPath { let entry = crate::HwMonPath::entry(self, index); - entry.attribute(crate::HwMonAttribute::name())?; - Ok(entry) + //entry.attribute(crate::HwMonAttribute::name())?; + entry + } + + /// Find a hardware monitor by capabilities + pub fn hwmon(&self, mut c: impl crate::capability::Capabilities) -> std::io::Result> { + Ok(crate::HwMonPath::all(self.root.clone())? + .filter_map(|p| p.ok()) + .filter(move |p| c.can(&p.capabilities()))) } /// Find a power supply entry by name - pub fn power_supply(&self, name: &str) -> crate::PowerSupplyPath { + pub fn power_supply_by_name(&self, name: &str) -> crate::PowerSupplyPath { crate::PowerSupplyPath::name(self, name) } + + /// Find a power supply by capabilities + pub fn power_supply(&self, mut c: impl crate::capability::Capabilities) -> std::io::Result> { + Ok(crate::PowerSupplyPath::all(self.root.clone())? + .filter_map(|p| p.ok()) + .filter(move |p| c.can(&p.capabilities()))) + } + + /// Find entities in a sysfs class by capabilities + pub fn class(&self, class: impl AsRef, mut c: impl crate::capability::Capabilities) -> std::io::Result> { + Ok(crate::BasicEntityPath::all(self.root.clone(), class)? + .filter_map(|p| p.ok()) + .filter(move |p| c.can(&p.capabilities())) + ) + } + + pub(crate) fn from_entity_path(entity: impl AsRef) -> Option { + Some(Self::path(entity.as_ref().parent()?.parent()?.parent()?.parent()?)) + } } impl std::default::Default for SysPath { @@ -60,3 +87,23 @@ impl AsRef for SysPath { self.root.as_path() } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::SysEntity; + + #[test] + fn syspath_root_recovery() -> std::io::Result<()> { + let sys = crate::SysPath::default(); + let all_ent: Vec<_> = crate::BasicEntityPath::all(&sys, "hwmon")?.collect(); + assert!(!all_ent.is_empty()); + for p in all_ent.into_iter() { + let p = p?; + let root_opt = p.root(); + assert!(root_opt.is_some()); + assert_eq!(sys.as_ref(), root_opt.unwrap().as_ref()); + } + Ok(()) + } +}