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
36as_ffi_with_context!(
37 Enumerator,
38 enumerator,
39 ffi::udev_enumerate,
40 ffi::udev_enumerate_ref
41);
42
43impl 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.
187pub type Devices<'a> = List<'a, Enumerator, Device>;
188
189impl<'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)]
215mod 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