sysfuss/
hwmon.rs

1use std::path::{Path, PathBuf};
2use std::convert::AsRef;
3use std::io::Result as IoResult;
4
5const HWMON_DIR_PATH: &'static str = "sys/class/hwmon/";
6
7use crate::SysEntityAttributesExt;
8
9/// Attribute representation.
10/// Attribute files are usually in the format `<type><number>_<item>`.
11#[derive(Clone, Copy, PartialEq, Eq)]
12#[cfg_attr(feature = "derive", derive(Debug))]
13pub struct HwMonAttribute {
14   inner: HwMonAttributeInner,
15}
16
17#[derive(Clone, Copy, PartialEq, Eq)]
18#[cfg_attr(feature = "derive", derive(Debug))]
19enum HwMonAttributeInner {
20    Name,
21    Standard {
22        ty: HwMonAttributeType,
23        number: u64,
24        item: HwMonAttributeItem,
25    },
26    Uevent,
27    Custom(&'static str),
28}
29
30impl std::str::FromStr for HwMonAttributeInner {
31    type Err = String;
32    
33    fn from_str(s: &str) -> Result<Self, Self::Err> {
34        match s {
35            "name" => Ok(Self::Name),
36            "uevent" => Ok(Self::Uevent),
37            s => {
38                let start = HwMonAttributeType::from_attr_start(s)?;
39                let end = HwMonAttributeItem::from_attr_end(s)?;
40                let trimmed = s.trim_start_matches(start.to_attr_str()).trim_end_matches(end.to_attr_str());
41                if trimmed.ends_with("_") {
42                    let index = trimmed.trim_end_matches('_').parse().map_err(|e| format!("Invalid number in attribute name {}: {}", s, e))?;
43                    Ok(Self::Standard { ty: start, number: index, item: end })
44                } else {
45                    Err(format!("Missing underscore in attribute name {}", s))
46                }
47            }
48        }
49    }
50}
51
52/// Attribute type in the format `<type><number>_<item>`
53#[derive(Clone, Copy, PartialEq, Eq)]
54#[cfg_attr(feature = "derive", derive(Debug))]
55pub enum HwMonAttributeType {
56    /// Voltage
57    In,
58    /// Temperature
59    Temp,
60    /// Fan
61    Fan,
62    /// Current
63    Curr,
64}
65
66impl HwMonAttributeType {
67    const fn to_attr_str(&self) -> &str {
68        match self {
69            Self::In => "in",
70            Self::Temp => "out",
71            Self::Fan => "fan",
72            Self::Curr => "curr",
73        }
74    }
75    
76    fn from_attr_start(s: &str) -> Result<Self, String> {
77        if s.starts_with(Self::In.to_attr_str()) {
78            Ok(Self::In)
79        } else if s.starts_with(Self::Temp.to_attr_str()) {
80            Ok(Self::Temp)
81        } else if s.starts_with(Self::Fan.to_attr_str()) {
82            Ok(Self::Fan)
83        } else if s.starts_with(Self::Curr.to_attr_str()) {
84            Ok(Self::Curr)
85        } else {
86            Err(format!("Unrecognised hwmon attribute type in name {}", s))
87        }
88    }
89}
90
91/// Attribute item in the format `<type><number>_<item>`
92#[derive(Clone, Copy, PartialEq, Eq)]
93#[cfg_attr(feature = "derive", derive(Debug))]
94pub enum HwMonAttributeItem {
95    /// Input
96    Input,
97    /// Minimum
98    Min,
99    /// Maximum
100    Max,
101    /// Readable name for another attribute
102    Label,
103}
104
105impl HwMonAttributeItem {
106    const fn to_attr_str(&self) -> &str {
107        match self {
108            Self::Input => "input",
109            Self::Min => "min",
110            Self::Max => "max",
111            Self::Label => "label"
112        }
113    }
114    
115    fn from_attr_end(s: &str) -> Result<Self, String> {
116        if s.ends_with(Self::Input.to_attr_str()) {
117            Ok(Self::Input)
118        } else if s.ends_with(Self::Min.to_attr_str()) {
119            Ok(Self::Min)
120        } else if s.ends_with(Self::Max.to_attr_str()) {
121            Ok(Self::Max)
122        } else {
123            Err(format!("Unrecognised hwmon attribute type in name {}", s))
124        }
125    }
126}
127
128impl HwMonAttribute {
129    /// `name` attribute
130    pub const fn name() -> Self {
131        Self {
132            inner: HwMonAttributeInner::Name
133        }
134    }
135    
136    /// Custom attribute in standard format
137    pub const fn new(ty: HwMonAttributeType, number: u64, item: HwMonAttributeItem) -> Self {
138        Self {
139            inner: HwMonAttributeInner::Standard { ty, number, item }
140        }
141    }
142    
143    /// `uevent` attribute
144    pub const fn uevent() -> Self {
145        Self {
146            inner: HwMonAttributeInner::Uevent
147        }
148    }
149
150    /// Custom entity attribute
151    pub const fn custom(attr: &'static str) -> Self {
152        Self {
153            inner: HwMonAttributeInner::Custom(attr)
154        }
155    }
156    
157    fn to_attr_str(&self) -> String {
158        match self.inner {
159            HwMonAttributeInner::Name => "name".to_string(),
160            HwMonAttributeInner::Standard { ty, number, item } => format!("{}{}_{}", ty.to_attr_str(), number, item.to_attr_str()),
161            HwMonAttributeInner::Uevent => "uevent".to_string(),
162            HwMonAttributeInner::Custom(s) => s.to_owned(),
163        }
164    }
165}
166
167impl crate::SysAttribute for HwMonAttribute {
168    fn filename(&self) -> PathBuf {
169        PathBuf::from(self.to_attr_str())
170    }
171}
172
173/// hwmon<number>/ directory
174#[cfg_attr(feature = "derive", derive(Debug, Clone))]
175pub struct HwMonPath {
176    path: PathBuf
177}
178
179impl HwMonPath {
180    pub(crate) fn all(root: impl AsRef<Path>) -> IoResult<impl Iterator<Item=IoResult<Self>>> {
181        let hwmon_dir_path = root.as_ref().join(HWMON_DIR_PATH);
182        hwmon_dir_path.read_dir()
183            .map(
184                |iter| iter.filter_map(
185                    |entry| entry.map(
186                        |entry| if crate::os_str_util::starts_with(&entry.file_name(), "hwmon".as_ref()) {
187                            Some(Self {
188                                path: entry.path(),
189                            })
190                        } else {
191                            None
192                        }).transpose()))
193    }
194    
195    /// Get a hwmon entry by index. 
196    /// This does not check if it exists.
197    pub(crate) fn entry(root: &crate::SysPath, i: u64) -> Self {
198        Self {
199            path: root.as_ref().join(HWMON_DIR_PATH).join(format!("hwmon{}", i)),
200        }
201    }
202    
203    /// Get a hwmon entry by name, if it exists
204    pub(crate) fn name(root: &crate::SysPath, name: &str) -> IoResult<Option<Self>> {
205        for entry in Self::all(root)? {
206            let entry = entry?;
207            let value: String = entry.attribute::<String, _>(HwMonAttribute::name()).map_err(|e| match e {
208                crate::EitherErr2::First(e) => e,
209                crate::EitherErr2::Second(_e) => panic!("Infallible"),
210            })?;
211            if value == name {
212                return Ok(Some(entry))
213            }
214        }
215        Ok(None)
216    }
217    
218    /// Get a hwmon attribute (file contents) by attribute filename.
219    /// It is recommended to use HwMon.attribute unless the attribute has a non-standard name
220    pub fn attribute_str(&self, name: &str) -> IoResult<String> {
221        std::fs::read_to_string(self.path.join(name))
222    }
223    
224    /// Get the path to a hwmon attribute.
225    /// Use `HwMonPath.as_ref().join(attribute_str)` for non-standard attributes
226    pub fn path_to(&self, attr: HwMonAttribute) -> PathBuf {
227        self.path.join(&attr.to_attr_str())
228    }
229}
230
231impl AsRef<Path> for HwMonPath {
232    fn as_ref(&self) -> &Path {
233        self.path.as_path()
234    }
235}
236
237impl crate::SysEntity for HwMonPath {
238    fn to_entity_path(self) -> crate::EntityPath {
239        crate::EntityPath::HwMon(self)
240    }
241    
242    fn name(&self) -> IoResult<String> {
243        self.attribute(HwMonAttribute::name()).map_err(|e| e.map_infallible_second())
244    }
245}
246
247impl crate::SysEntityAttributes<HwMonAttribute> for HwMonPath {
248    fn capabilities(&self) -> Vec<HwMonAttribute> {
249        if let Ok(dir_iter) = self.path.read_dir() {
250            dir_iter.filter_map(
251                |entry| entry.ok().filter(|entry| entry.path().is_file()).and_then(|entry| entry.file_name().into_string().ok()).and_then(|s| s.parse::<HwMonAttributeInner>().ok())
252            ).map(|inner| HwMonAttribute {inner}).collect()
253        } else {
254            Vec::with_capacity(0)
255        }
256    }
257}
258
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263    use crate::SysEntityAttributes;
264
265    #[test]
266    fn hwmon_all() -> std::io::Result<()> {
267        let sys = crate::SysPath::default();
268        let all_hwmon: Vec<_> = HwMonPath::all(sys)?.collect();
269        assert!(!all_hwmon.is_empty());
270        for hwmon in all_hwmon.into_iter() {
271            let hwmon = hwmon?;
272            assert!(hwmon.attribute::<String, _>(HwMonAttribute::name()).map_err(|e| e.map_infallible_second())? != "");
273            assert!(!hwmon.attribute::<String, _>(HwMonAttribute::name()).map_err(|e| e.map_infallible_second())?.ends_with("\n"));
274            assert!(!hwmon.capabilities().is_empty());
275            assert!(hwmon.capabilities().contains(&HwMonAttribute::name()))
276        }
277        Ok(())
278    }
279
280    #[test]
281    fn hwmon_capabilities() -> std::io::Result<()> {
282        let sys = crate::SysPath::default();
283        if !sys.hwmon_by_name("amdgpu").is_ok() {
284            // skip if system has no AMD GPU
285            eprintln!("hwmon test skipped since amdgpu does not exist (maybe running on a laptop PC?)");
286            return Ok(())
287        }
288        let hwmon = sys.hwmon(crate::capability::attributes([
289            HwMonAttribute::name(),
290            HwMonAttribute::new(HwMonAttributeType::Fan, 1, HwMonAttributeItem::Input),
291            HwMonAttribute::new(HwMonAttributeType::Fan, 1, HwMonAttributeItem::Min),
292            HwMonAttribute::new(HwMonAttributeType::Fan, 1, HwMonAttributeItem::Max)
293        ].into_iter()))?.next().expect("Missing capable amdgpu");
294        assert_eq!(hwmon.attribute::<String, _>(HwMonAttribute::name()).expect("name capable but also incapable"), "amdgpu");
295        Ok(())
296    }
297}