sysfuss/
basic.rs

1use std::path::{Path, PathBuf};
2use std::convert::AsRef;
3use std::io::Result as IoResult;
4
5const SYS_CLASS_PATH: &str = "sys/class";
6
7/// Generic entity path with basic functionality
8#[cfg_attr(feature = "derive", derive(Debug, Clone))]
9pub struct BasicEntityPath {
10    path: PathBuf
11}
12
13impl BasicEntityPath {
14    /// Create a new BasicEntityPath for manipulating path
15    pub fn new(path: impl AsRef<Path>) -> Self {
16        Self {
17            path: path.as_ref().to_path_buf(),
18        }
19    }
20    
21    pub(crate) fn all(root: impl AsRef<Path>, class: impl AsRef<Path>) -> IoResult<impl Iterator<Item=IoResult<Self>>> {
22        let dir_path = root.as_ref().join(SYS_CLASS_PATH).join(class);
23        Ok(dir_path.read_dir()?
24                .filter_map(
25                    |entry| entry.map(
26                        |entry| if entry.path().is_file() { None } else { Some(Self::new(entry.path())) }
27                    ).transpose()))
28    }
29    
30    /// Filter out attributes that do not exist on this entity
31    pub fn filter_capabilities<A: crate::SysAttribute>(&self, attributes: impl Iterator<Item=A>) -> impl Iterator<Item=A> {
32        let found_attrs: Vec<PathBuf> = if let Ok(dir_iter) = self.path.read_dir() {
33            dir_iter.filter_map(
34                |entry| entry.ok().filter(|entry| entry.path().is_file()).map(|entry| PathBuf::from(entry.file_name()))
35            ).collect()
36        } else {
37            Vec::with_capacity(0)
38        };
39        
40        attributes.filter(move |attr| found_attrs.contains(&attr.filename()))
41    }
42}
43
44impl AsRef<Path> for BasicEntityPath {
45    fn as_ref(&self) -> &Path {
46        self.path.as_path()
47    }
48}
49
50impl crate::SysEntity for BasicEntityPath {
51    fn to_entity_path(self) -> crate::EntityPath {
52        crate::EntityPath::Generic(self)
53    }
54    
55    fn name(&self) -> IoResult<String> {
56        if let Some(s) = self.path.file_name().map(|name| name.to_str().map(|s| s.to_owned())).flatten() {
57            Ok(s)
58        } else {
59            Err(std::io::Error::from_raw_os_error(std::io::ErrorKind::InvalidInput as _))
60        }
61    }
62}
63
64impl crate::SysEntityAttributes<String> for BasicEntityPath {
65    fn capabilities(&self) -> Vec<String> {
66        if let Ok(dir_iter) = self.path.read_dir() {
67            dir_iter.filter_map(
68                |entry| entry.ok().filter(|entry| entry.path().is_file()).and_then(|entry| entry.file_name().to_str().map(|s| s.to_owned()))
69            ).collect()
70        } else {
71            Vec::with_capacity(0)
72        }
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use crate::SysEntity;
80    use crate::SysEntityRawExt;
81
82    #[test]
83    fn basic_entity_all() -> std::io::Result<()> {
84        let sys = crate::SysPath::default();
85        let all_basic: Vec<_> = BasicEntityPath::all(sys, "drm")?.collect();
86        assert!(!all_basic.is_empty());
87        for ent in all_basic.into_iter() {
88            assert!(ent?.attribute_str("uevent")? != "");
89        }
90        Ok(())
91    }
92
93    #[test]
94    fn basic_capabilities() -> std::io::Result<()> {
95        let sys = crate::SysPath::default();
96        if std::fs::read_to_string("/sys/class/hwmon/hwmon0/name")?.trim() != "nvme" {
97            // skip if system has different hwmons
98            // TODO account for different test systems
99            eprintln!("basic entity test skipped since hwmon0 is not nvme (maybe running on a different PC?)");
100            return Ok(())
101        }
102        let basic = sys.class("hwmon", crate::capability::attributes([
103            "name".to_string(),
104            "temp1_alarm".to_string(),
105            "temp1_crit".to_string(),
106        ].into_iter()))?.next().expect("Missing any hwmon");
107        let attr_value = crate::SysEntityAttributesExt::attribute::<String, _>(&basic, "name".to_owned()).expect("name capable but also incapable");
108        println!("Attribute ./name = '{}'", attr_value);
109        assert!(attr_value == "nvme");
110        Ok(())
111    }
112
113    #[test]
114    fn basic_drm_capabilities() -> std::io::Result<()> {
115        let sys = crate::SysPath::default();
116        let expected_dev = if let Ok(dev) = std::fs::read_to_string("/sys/class/drm/card1/dev") {
117            dev.trim().to_owned()
118        } else {
119            // skip if system has different hwmons
120            // TODO account for different test systems
121            eprintln!("basic entity test skipped since drm card0 does not exist (maybe running on a different PC?)");
122            return Ok(())
123        };
124        let basic = sys.class("drm", crate::capability::attributes([
125            "dev".to_string(),
126            "uevent".to_string(),
127        ].into_iter()))?.filter(|basic| basic.name().expect("no name").starts_with("card")).next().expect("Missing drm");
128        let attr_value = crate::SysEntityAttributesExt::attribute::<String, _>(&basic, "dev".to_owned()).expect("dev capable but also incapable");
129        println!("Attribute ./dev = '{}'", attr_value);
130        assert!(attr_value == expected_dev);
131        Ok(())
132    }
133}