| 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 | |