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