1use std::ffi::OsStr;
2use std::io::Result;
3use std::marker::PhantomData;
4use std::path::Path;
5
6use Udev;
7use {ffi, list::List, util};
8
9use {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.
16pub struct Enumerator {
17 udev: Udev,
18 enumerator: *mut ffi::udev_enumerate,
19}
20
21impl 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
30impl Drop for Enumerator {
31 fn drop(&mut self) {
32 unsafe { ffi::udev_enumerate_unref(self.enumerator) };
33 }
34}
35
36#[cfg(feature = "send")]
37unsafe impl Send for Enumerator {}
38#[cfg(feature = "sync")]
39unsafe impl Sync for Enumerator {}
40
41as_ffi_with_context!(
42 Enumerator,
43 enumerator,
44 ffi::udev_enumerate,
45 ffi::udev_enumerate_ref
46);
47
48impl 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.
192pub type Devices<'a> = List<'a, Enumerator, Device>;
193
194impl<'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)]
220mod 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