1 | use std::ffi::OsStr; |
2 | use std::io::Result; |
3 | use std::marker::PhantomData; |
4 | use std::path::Path; |
5 | |
6 | use Udev; |
7 | use {ffi, list::List, util}; |
8 | |
9 | use {AsRaw, AsRawWithContext, Device, FromRaw}; |
10 | |
11 | /// An enumeration context. |
12 | /// |
13 | /// An Enumerator scans `/sys` for devices matching its filters. Filters are added to an Enumerator |
14 | /// by calling its `match_*` and `nomatch_*` methods. After the filters are setup, the |
15 | /// `scan_devices()` method finds devices in `/sys` that match the filters. |
16 | pub struct Enumerator { |
17 | udev: Udev, |
18 | enumerator: *mut ffi::udev_enumerate, |
19 | } |
20 | |
21 | impl Clone for Enumerator { |
22 | fn clone(&self) -> Self { |
23 | Self { |
24 | udev: self.udev.clone(), |
25 | enumerator: unsafe { ffi::udev_enumerate_ref(self.enumerator) }, |
26 | } |
27 | } |
28 | } |
29 | |
30 | impl Drop for Enumerator { |
31 | fn drop(&mut self) { |
32 | unsafe { ffi::udev_enumerate_unref(self.enumerator) }; |
33 | } |
34 | } |
35 | |
36 | as_ffi_with_context!( |
37 | Enumerator, |
38 | enumerator, |
39 | ffi::udev_enumerate, |
40 | ffi::udev_enumerate_ref |
41 | ); |
42 | |
43 | impl Enumerator { |
44 | /// Creates a new Enumerator. |
45 | pub fn new() -> Result<Self> { |
46 | // Create a new Udev context for this enumeration |
47 | let udev = Udev::new()?; |
48 | Self::with_udev(udev) |
49 | } |
50 | |
51 | /// Creates a new `Enumerator` with an existing `Udev` instance |
52 | pub fn with_udev(udev: Udev) -> Result<Self> { |
53 | let ptr = try_alloc!(unsafe { ffi::udev_enumerate_new(udev.as_raw()) }); |
54 | Ok(Self { |
55 | udev, |
56 | enumerator: ptr, |
57 | }) |
58 | } |
59 | |
60 | /// Adds a filter that matches only initialized devices. |
61 | pub fn match_is_initialized(&mut self) -> Result<()> { |
62 | util::errno_to_result(unsafe { |
63 | ffi::udev_enumerate_add_match_is_initialized(self.enumerator) |
64 | }) |
65 | } |
66 | |
67 | /// Adds a filter that matches only devices that belong to the given kernel subsystem. |
68 | pub fn match_subsystem<T: AsRef<OsStr>>(&mut self, subsystem: T) -> Result<()> { |
69 | let subsystem = util::os_str_to_cstring(subsystem)?; |
70 | |
71 | util::errno_to_result(unsafe { |
72 | ffi::udev_enumerate_add_match_subsystem(self.enumerator, subsystem.as_ptr()) |
73 | }) |
74 | } |
75 | |
76 | /// Adds a filter that matches only devices with the given attribute value. |
77 | pub fn match_attribute<T: AsRef<OsStr>, U: AsRef<OsStr>>( |
78 | &mut self, |
79 | attribute: T, |
80 | value: U, |
81 | ) -> Result<()> { |
82 | let attribute = util::os_str_to_cstring(attribute)?; |
83 | let value = util::os_str_to_cstring(value)?; |
84 | |
85 | util::errno_to_result(unsafe { |
86 | ffi::udev_enumerate_add_match_sysattr( |
87 | self.enumerator, |
88 | attribute.as_ptr(), |
89 | value.as_ptr(), |
90 | ) |
91 | }) |
92 | } |
93 | |
94 | /// Adds a filter that matches only devices with the given kernel device name. |
95 | pub fn match_sysname<T: AsRef<OsStr>>(&mut self, sysname: T) -> Result<()> { |
96 | let sysname = util::os_str_to_cstring(sysname)?; |
97 | |
98 | util::errno_to_result(unsafe { |
99 | ffi::udev_enumerate_add_match_sysname(self.enumerator, sysname.as_ptr()) |
100 | }) |
101 | } |
102 | |
103 | /// Adds a filter that matches only devices with the given property value. |
104 | pub fn match_property<T: AsRef<OsStr>, U: AsRef<OsStr>>( |
105 | &mut self, |
106 | property: T, |
107 | value: U, |
108 | ) -> Result<()> { |
109 | let property = util::os_str_to_cstring(property)?; |
110 | let value = util::os_str_to_cstring(value)?; |
111 | |
112 | util::errno_to_result(unsafe { |
113 | ffi::udev_enumerate_add_match_property( |
114 | self.enumerator, |
115 | property.as_ptr(), |
116 | value.as_ptr(), |
117 | ) |
118 | }) |
119 | } |
120 | |
121 | /// Adds a filter that matches only devices with the given tag. |
122 | pub fn match_tag<T: AsRef<OsStr>>(&mut self, tag: T) -> Result<()> { |
123 | let tag = util::os_str_to_cstring(tag)?; |
124 | |
125 | util::errno_to_result(unsafe { |
126 | ffi::udev_enumerate_add_match_tag(self.enumerator, tag.as_ptr()) |
127 | }) |
128 | } |
129 | |
130 | /// Includes the parent device and all devices in the subtree of the parent device. |
131 | pub fn match_parent(&mut self, parent: &Device) -> Result<()> { |
132 | util::errno_to_result(unsafe { |
133 | ffi::udev_enumerate_add_match_parent(self.enumerator, parent.as_raw()) |
134 | }) |
135 | } |
136 | |
137 | /// Adds a filter that matches only devices that don't belong to the given kernel subsystem. |
138 | pub fn nomatch_subsystem<T: AsRef<OsStr>>(&mut self, subsystem: T) -> Result<()> { |
139 | let subsystem = util::os_str_to_cstring(subsystem)?; |
140 | |
141 | util::errno_to_result(unsafe { |
142 | ffi::udev_enumerate_add_nomatch_subsystem(self.enumerator, subsystem.as_ptr()) |
143 | }) |
144 | } |
145 | |
146 | /// Adds a filter that matches only devices that don't have the the given attribute value. |
147 | pub fn nomatch_attribute<T: AsRef<OsStr>, U: AsRef<OsStr>>( |
148 | &mut self, |
149 | attribute: T, |
150 | value: U, |
151 | ) -> Result<()> { |
152 | let attribute = util::os_str_to_cstring(attribute)?; |
153 | let value = util::os_str_to_cstring(value)?; |
154 | |
155 | util::errno_to_result(unsafe { |
156 | ffi::udev_enumerate_add_nomatch_sysattr( |
157 | self.enumerator, |
158 | attribute.as_ptr(), |
159 | value.as_ptr(), |
160 | ) |
161 | }) |
162 | } |
163 | |
164 | /// Includes the device with the given syspath. |
165 | pub fn add_syspath<T: AsRef<OsStr>>(&mut self, syspath: T) -> Result<()> { |
166 | let syspath = util::os_str_to_cstring(syspath)?; |
167 | |
168 | util::errno_to_result(unsafe { |
169 | ffi::udev_enumerate_add_syspath(self.enumerator, syspath.as_ptr()) |
170 | }) |
171 | } |
172 | |
173 | /// Scans `/sys` for devices matching the attached filters. |
174 | /// |
175 | /// The devices will be sorted in dependency order. |
176 | pub fn scan_devices(&mut self) -> Result<List<Enumerator, Device>> { |
177 | util::errno_to_result(unsafe { ffi::udev_enumerate_scan_devices(self.enumerator) })?; |
178 | |
179 | Ok(Devices { |
180 | entry: unsafe { ffi::udev_enumerate_get_list_entry(self.enumerator) }, |
181 | phantom: PhantomData, |
182 | }) |
183 | } |
184 | } |
185 | |
186 | /// Iterator over devices. |
187 | pub type Devices<'a> = List<'a, Enumerator, Device>; |
188 | |
189 | impl<'a> Iterator for Devices<'a> { |
190 | type Item = Device; |
191 | |
192 | fn next(&mut self) -> Option<Device> { |
193 | while !self.entry.is_null() { |
194 | let syspath: &Path = Path::new(unsafe { |
195 | util::ptr_to_os_str_unchecked(ptr:ffi::udev_list_entry_get_name(self.entry)) |
196 | }); |
197 | |
198 | self.entry = unsafe { ffi::udev_list_entry_get_next(self.entry) }; |
199 | |
200 | match Device::from_syspath(syspath) { |
201 | Ok(d: Device) => return Some(d), |
202 | Err(_) => continue, |
203 | }; |
204 | } |
205 | |
206 | None |
207 | } |
208 | |
209 | fn size_hint(&self) -> (usize, Option<usize>) { |
210 | (0, None) |
211 | } |
212 | } |
213 | |
214 | #[cfg (test)] |
215 | mod tests { |
216 | use super::*; |
217 | use crate::{AsRawWithContext, FromRawWithContext}; |
218 | |
219 | #[test ] |
220 | fn create_enumerator() { |
221 | Enumerator::new().unwrap(); |
222 | } |
223 | |
224 | #[test ] |
225 | fn round_trip_to_raw_pointers() { |
226 | let enumerator = Enumerator::new().unwrap(); |
227 | |
228 | // Round-trip this to raw pointers and back again |
229 | let (udev, ptr) = enumerator.into_raw_with_context(); |
230 | |
231 | let mut enumerator = unsafe { Enumerator::from_raw_with_context(udev, ptr) }; |
232 | |
233 | // Everything should still work just the same after round-tripping |
234 | let _ = enumerator.scan_devices().unwrap().collect::<Vec<_>>(); |
235 | } |
236 | |
237 | #[test ] |
238 | fn test_enumeration() { |
239 | fn find_hidraws(en: &mut Enumerator) -> Devices<'_> { |
240 | en.match_is_initialized().unwrap(); |
241 | en.match_subsystem("hidraw" ).unwrap(); |
242 | en.scan_devices().unwrap() |
243 | } |
244 | |
245 | let mut en = Enumerator::new().unwrap(); |
246 | for dev in find_hidraws(&mut en) { |
247 | println!("Found a hidraw at {:?}" , dev.devnode()); |
248 | } |
249 | } |
250 | |
251 | // The above test which limits devices to `hidraw` did not reproduce the crash on libudev 215 |
252 | // caused by the use of a bogus udev context. Clearly it's important to test all enumeration |
253 | // pathways. |
254 | // |
255 | // This test is intended to reproduce https://github.com/Smithay/udev-rs/issues/18 when run on |
256 | // a system like Debian 8 "jessie" which runs an older libudev |
257 | #[test ] |
258 | fn test_enumerate_all() { |
259 | let mut en = Enumerator::new().unwrap(); |
260 | |
261 | for dev in en.scan_devices().unwrap() { |
262 | println!("Found a device at {:?}" , dev.devnode()); |
263 | } |
264 | } |
265 | } |
266 | |