udev/
enumerator.rs

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::new(unsafe {
200                util::ptr_to_os_str_unchecked(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) => 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}