1use std::{
2 collections::HashMap,
3 ffi::{OsStr, OsString},
4 io::{BufRead, BufReader},
5 os::unix::ffi::OsStringExt,
6 path::{Path, PathBuf},
7};
8
9pub struct Enumerator {
10 subsystem: Option<String>,
11}
12
13const UDEV_ROOT: &str = "/sys";
14const DEV_ROOT: &str = "/dev";
15
16impl Enumerator {
17 pub fn new() -> std::io::Result<Enumerator> {
18 Ok(Enumerator { subsystem: None })
19 }
20
21 pub fn match_subsystem(&mut self, subsystem: &str) -> std::io::Result<()> {
22 self.subsystem = Some(subsystem.to_owned());
23 Ok(())
24 }
25
26 pub fn scan_devices(&self) -> std::io::Result<Box<dyn Iterator<Item = Device>>> {
27 let mut devices = vec![];
28 let mut device_path = PathBuf::from(UDEV_ROOT);
29 device_path.push("class");
30 device_path.push(self.subsystem.as_ref().unwrap());
31 for dir_entry in device_path.read_dir()? {
32 let dir_entry = dir_entry?;
33
34 if let Ok(device) = Device::from_syspath(&dir_entry.path()) {
35 devices.push(device);
36 }
37 }
38 Ok(Box::new(devices.into_iter()))
39 }
40}
41
42#[derive(Debug)]
43#[allow(dead_code)]
44pub struct Device {
45 system_path: PathBuf,
46 subsystem: Option<String>,
47 driver: Option<String>,
48 devnode: Option<PathBuf>,
49 attributes: HashMap<String, OsString>,
50 properties: HashMap<String, OsString>,
51 parent: Option<Box<Device>>,
52}
53
54impl Device {
55 pub fn from_syspath(path: &Path) -> std::io::Result<Device> {
56 let path = path.canonicalize()?;
57 let mut parent = None;
58
59 let mut parent_path = path.parent();
61 loop {
62 let Some(path) = parent_path else {
63 break;
64 };
65 let uevent = path.join("uevent");
66 if uevent.exists() && uevent.is_file() {
67 parent = Self::from_syspath(path).ok().map(|parent| Box::new(parent));
68 break;
69 }
70 parent_path = path.parent();
71 }
72
73 let uevent = path.join("uevent");
74 if !uevent.exists() || !uevent.is_file() {
75 return Err(std::io::Error::from(std::io::ErrorKind::NotFound));
76 }
77
78 let mut properties = HashMap::new();
79 let uevent = std::fs::File::open(uevent)?;
80 for line in BufReader::new(uevent).lines() {
81 let Ok(line) = line else {
82 break;
83 };
84 let Some((key, value)) = line.split_once('=') else {
85 continue;
86 };
87 properties.insert(
88 key.to_owned(),
89 OsString::from_vec(value.as_bytes().to_vec()),
90 );
91 }
92
93 let subsystem = path.join("subsystem").canonicalize().ok().and_then(|dir| {
94 dir.file_name()
95 .and_then(|v| v.to_str())
96 .map(|name| name.to_string())
97 });
98 let driver = path.join("driver").canonicalize().ok().and_then(|dir| {
99 dir.file_name()
100 .and_then(|v| v.to_str())
101 .map(|name| name.to_string())
102 });
103
104 let mut attrs = HashMap::new();
105 Self::update_attrs(&path, &path, &mut attrs);
106
107 let mut devnode = None;
109 if let Some(devname) = properties.get("DEVNAME") {
110 let mut tmp_dev = Path::new(DEV_ROOT).to_path_buf();
111 tmp_dev.push(devname);
112 devnode = Some(tmp_dev)
113 }
114
115 Ok(Device {
116 system_path: path.to_owned(),
117 subsystem,
118 driver,
119 attributes: attrs,
120 parent,
121 properties,
122 devnode,
123 })
124 }
125
126 fn update_attrs(path: &Path, base: &Path, attrs: &mut HashMap<String, OsString>) {
127 let Ok(read_dir) = path.read_dir() else {
128 return;
129 };
130 for entry in read_dir {
131 let Ok(entry) = entry else {
132 continue;
133 };
134 let entry_path = entry.path();
135 if entry_path.is_symlink() {
136 continue;
137 }
138 let entry_name = entry_path.file_name().and_then(|v| v.to_str());
139 if entry_name == Some("uevent") || entry_name == Some("dev") {
140 continue;
141 }
142
143 if entry_path.is_dir() {
144 let mut sub_dir = entry_path.clone();
145 sub_dir.push("uevent");
146 if !sub_dir.exists() {
147 Self::update_attrs(&entry_path, base, attrs);
148 continue;
149 }
150 }
151
152 if entry_path.is_file() {
153 let Some(name) = entry_path
154 .strip_prefix(base)
155 .ok()
156 .map(|name| name.as_os_str())
157 .map(|name| name.to_str())
158 .flatten()
159 .map(|v| v.to_owned())
160 else {
161 continue;
162 };
163 let Ok(mut value) = std::fs::read(entry_path) else {
164 continue;
165 };
166 if value.last() == Some(&b'\n') {
168 value.pop();
169 }
170 attrs.insert(name, OsString::from_vec(value));
171 continue;
172 }
173 }
174 }
175
176 pub fn parent_with_subsystem(
177 &self,
178 parent_subsystem: &str,
179 ) -> std::io::Result<Option<&Device>> {
180 let mut parent = &self.parent;
181 loop {
182 let Some(unwrapped_parent) = parent else {
183 return Ok(None);
184 };
185
186 if unwrapped_parent.subsystem.as_deref() == Some(parent_subsystem) {
187 return Ok(Some(&unwrapped_parent));
188 }
189
190 parent = &unwrapped_parent.parent;
191 }
192 }
193
194 pub fn parent_with_subsystem_devtype(
195 &self,
196 parent_subsystem: &str,
197 devtype: &str,
198 ) -> std::io::Result<Option<&Device>> {
199 let mut parent = &self.parent;
200 loop {
201 let Some(unwrapped_parent) = parent else {
202 println!("Couldn't get parent device");
203 return Ok(None);
204 };
205
206 if unwrapped_parent.subsystem.as_deref() == Some(parent_subsystem)
207 && unwrapped_parent
208 .property_value("DEVTYPE")
209 .map(|x| x.to_str())
210 .flatten()
211 == Some(devtype)
212 {
213 return Ok(Some(&unwrapped_parent));
214 }
215 parent = &unwrapped_parent.parent;
216 }
217 }
218
219 pub fn property_value(&self, key: &str) -> Option<&OsStr> {
220 self.properties.get(key).map(|x| x.as_os_str())
221 }
222
223 pub fn attribute_value(&self, key: &str) -> Option<&OsStr> {
224 self.attributes.get(key).map(|x| x.as_os_str())
225 }
226
227 pub fn devnode(&self) -> Option<&Path> {
228 self.devnode.as_deref()
229 }
230
231 pub fn syspath(&self) -> &Path {
232 self.system_path.as_path()
233 }
234}