1 | use std::str; |
2 | |
3 | use std::ffi::{CStr, OsStr}; |
4 | use std::io::Result; |
5 | use std::marker::PhantomData; |
6 | use std::path::Path; |
7 | use std::ptr; |
8 | use std::str::FromStr; |
9 | |
10 | use libc::{c_char, dev_t}; |
11 | |
12 | use list::{Entry, EntryList}; |
13 | use Udev; |
14 | use {ffi, util}; |
15 | |
16 | use {AsRaw, FromRaw}; |
17 | |
18 | /// A structure that provides access to sysfs/kernel devices. |
19 | pub struct Device { |
20 | udev: Udev, |
21 | device: *mut ffi::udev_device, |
22 | } |
23 | |
24 | impl std::fmt::Debug for Device { |
25 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
26 | f&mut DebugStruct<'_, '_>.debug_struct("Device" ) |
27 | .field("initialized" , &self.is_initialized()) |
28 | .field("device_major_minor_number" , &self.devnum()) |
29 | .field("system_path" , &self.syspath()) |
30 | .field("device_path" , &self.devpath()) |
31 | .field("device_node" , &self.devnode()) |
32 | .field("subsystem_name" , &self.subsystem()) |
33 | .field("system_name" , &self.sysname()) |
34 | .field("instance_number" , &self.sysnum()) |
35 | .field("device_type" , &self.devtype()) |
36 | .field("driver" , &self.driver()) |
37 | .field("action" , &self.action()) |
38 | .field(name:"parent" , &self.parent()) |
39 | .finish() |
40 | } |
41 | } |
42 | |
43 | impl Clone for Device { |
44 | fn clone(&self) -> Self { |
45 | Self { |
46 | udev: self.udev.clone(), |
47 | device: unsafe { ffi::udev_device_ref(self.device) }, |
48 | } |
49 | } |
50 | } |
51 | |
52 | impl Drop for Device { |
53 | fn drop(&mut self) { |
54 | unsafe { |
55 | ffi::udev_device_unref(self.device); |
56 | } |
57 | } |
58 | } |
59 | |
60 | as_ffi_with_context!(Device, device, ffi::udev_device, ffi::udev_device_ref); |
61 | |
62 | /// A convenience alias for a list of properties, bound to a device. |
63 | pub type Properties<'a> = EntryList<'a, Device>; |
64 | |
65 | /// A convenience alias for a list of attributes, bound to a device. |
66 | pub struct Attributes<'a> { |
67 | entries: EntryList<'a, Device>, |
68 | device: &'a Device, |
69 | } |
70 | |
71 | impl Device { |
72 | /// Creates a device for a given syspath. |
73 | /// |
74 | /// The `syspath` parameter should be a path to the device file within the `sysfs` file system, |
75 | /// e.g., `/sys/devices/virtual/tty/tty0`. |
76 | pub fn from_syspath(syspath: &Path) -> Result<Self> { |
77 | // Create a new Udev context for this device |
78 | // It would be more efficient to allow callers to create just one context and use multiple |
79 | // devices, however that would be an API-breaking change. |
80 | // |
81 | // When devices are enumerated using an `Enumerator`, it will use |
82 | // `from_syspath_with_context` which can reuse the existing `Udev` context to avoid this |
83 | // extra overhead. |
84 | let udev = Udev::new()?; |
85 | |
86 | Self::from_syspath_with_context(udev, syspath) |
87 | } |
88 | |
89 | /// Creates a device for a given syspath, using an existing `Udev` instance rather than |
90 | /// creating one automatically. |
91 | /// |
92 | /// The `syspath` parameter should be a path to the device file within the `sysfs` file system, |
93 | /// e.g., `/sys/devices/virtual/tty/tty0`. |
94 | pub fn from_syspath_with_context(udev: Udev, syspath: &Path) -> Result<Self> { |
95 | let syspath = util::os_str_to_cstring(syspath)?; |
96 | |
97 | let ptr = try_alloc!(unsafe { |
98 | ffi::udev_device_new_from_syspath(udev.as_raw(), syspath.as_ptr()) |
99 | }); |
100 | |
101 | Ok(Self::from_raw(udev, ptr)) |
102 | } |
103 | |
104 | /// Creates a rust `Device` given an already created libudev `ffi::udev_device*` and a |
105 | /// corresponding `Udev` instance from which the device was created. |
106 | /// |
107 | /// This guarantees that the `Udev` will live longer than the corresponding `Device` |
108 | pub(crate) fn from_raw(udev: Udev, ptr: *mut ffi::udev_device) -> Self { |
109 | Self { udev, device: ptr } |
110 | } |
111 | |
112 | /// Checks whether the device has already been handled by udev. |
113 | /// |
114 | /// When a new device is connected to the system, udev initializes the device by setting |
115 | /// permissions, renaming network devices, and possibly other initialization routines. This |
116 | /// method returns `true` if udev has performed all of its work to initialize this device. |
117 | /// |
118 | /// This method only applies to devices with device nodes or network interfaces. All other |
119 | /// devices return `true` by default. |
120 | pub fn is_initialized(&self) -> bool { |
121 | unsafe { ffi::udev_device_get_is_initialized(self.device) > 0 } |
122 | } |
123 | |
124 | /// Gets the device's major/minor number. |
125 | pub fn devnum(&self) -> Option<dev_t> { |
126 | match unsafe { ffi::udev_device_get_devnum(self.device) } { |
127 | 0 => None, |
128 | n => Some(n), |
129 | } |
130 | } |
131 | |
132 | /// Returns the syspath of the device. |
133 | /// |
134 | /// The path is an absolute path and includes the sys mount point. For example, the syspath for |
135 | /// `tty0` could be `/sys/devices/virtual/tty/tty0`, which includes the sys mount point, |
136 | /// `/sys`. |
137 | pub fn syspath(&self) -> &Path { |
138 | Path::new(unsafe { |
139 | util::ptr_to_os_str_unchecked(ffi::udev_device_get_syspath(self.device)) |
140 | }) |
141 | } |
142 | |
143 | /// Returns the kernel devpath value of the device. |
144 | /// |
145 | /// The path does not contain the sys mount point, but does start with a `/`. For example, the |
146 | /// devpath for `tty0` could be `/devices/virtual/tty/tty0`. |
147 | pub fn devpath(&self) -> &OsStr { |
148 | unsafe { util::ptr_to_os_str_unchecked(ffi::udev_device_get_devpath(self.device)) } |
149 | } |
150 | |
151 | /// Returns the path to the device node belonging to the device. |
152 | /// |
153 | /// The path is an absolute path and starts with the device directory. For example, the device |
154 | /// node for `tty0` could be `/dev/tty0`. |
155 | pub fn devnode(&self) -> Option<&Path> { |
156 | unsafe { util::ptr_to_os_str(ffi::udev_device_get_devnode(self.device)) } |
157 | .map(|path| Path::new(path)) |
158 | } |
159 | |
160 | /// Returns the parent of the device. |
161 | pub fn parent(&self) -> Option<Self> { |
162 | let ptr = unsafe { ffi::udev_device_get_parent(self.device) }; |
163 | |
164 | if ptr.is_null() { |
165 | return None; |
166 | } |
167 | |
168 | Some(Self::from_raw(self.udev.clone(), unsafe { |
169 | ffi::udev_device_ref(ptr) |
170 | })) |
171 | } |
172 | |
173 | /// Returns the parent of the device with the matching subsystem and devtype if any. |
174 | pub fn parent_with_subsystem<T: AsRef<OsStr>>(&self, subsystem: T) -> Result<Option<Self>> { |
175 | let subsystem = util::os_str_to_cstring(subsystem)?; |
176 | let ptr = unsafe { |
177 | ffi::udev_device_get_parent_with_subsystem_devtype( |
178 | self.device, |
179 | subsystem.as_ptr(), |
180 | ptr::null(), |
181 | ) |
182 | }; |
183 | |
184 | if ptr.is_null() { |
185 | return Ok(None); |
186 | } |
187 | |
188 | Ok(Some(Self::from_raw(self.udev.clone(), unsafe { |
189 | ffi::udev_device_ref(ptr) |
190 | }))) |
191 | } |
192 | |
193 | /// Returns the parent of the device with the matching subsystem and devtype if any. |
194 | pub fn parent_with_subsystem_devtype<T: AsRef<OsStr>, U: AsRef<OsStr>>( |
195 | &self, |
196 | subsystem: T, |
197 | devtype: U, |
198 | ) -> Result<Option<Self>> { |
199 | let subsystem = util::os_str_to_cstring(subsystem)?; |
200 | let devtype = util::os_str_to_cstring(devtype)?; |
201 | let ptr = unsafe { |
202 | ffi::udev_device_get_parent_with_subsystem_devtype( |
203 | self.device, |
204 | subsystem.as_ptr(), |
205 | devtype.as_ptr(), |
206 | ) |
207 | }; |
208 | |
209 | if ptr.is_null() { |
210 | return Ok(None); |
211 | } |
212 | |
213 | Ok(Some(Self::from_raw(self.udev.clone(), unsafe { |
214 | ffi::udev_device_ref(ptr) |
215 | }))) |
216 | } |
217 | |
218 | /// Returns the subsystem name of the device. |
219 | /// |
220 | /// The subsystem name is a string that indicates which kernel subsystem the device belongs to. |
221 | /// Examples of subsystem names are `tty`, `vtconsole`, `block`, `scsi`, and `net`. |
222 | pub fn subsystem(&self) -> Option<&OsStr> { |
223 | unsafe { util::ptr_to_os_str(ffi::udev_device_get_subsystem(self.device)) } |
224 | } |
225 | |
226 | /// Returns the kernel device name for the device. |
227 | /// |
228 | /// The sysname is a string that differentiates the device from others in the same subsystem. |
229 | /// For example, `tty0` is the sysname for a TTY device that differentiates it from others, |
230 | /// such as `tty1`. |
231 | pub fn sysname(&self) -> &OsStr { |
232 | unsafe { util::ptr_to_os_str_unchecked(ffi::udev_device_get_sysname(self.device)) } |
233 | } |
234 | |
235 | /// Returns the instance number of the device. |
236 | /// |
237 | /// The instance number is used to differentiate many devices of the same type. For example, |
238 | /// `/dev/tty0` and `/dev/tty1` are both TTY devices but have instance numbers of 0 and 1, |
239 | /// respectively. |
240 | /// |
241 | /// Some devices don't have instance numbers, such as `/dev/console`, in which case the method |
242 | /// returns `None`. |
243 | pub fn sysnum(&self) -> Option<usize> { |
244 | let ptr = unsafe { ffi::udev_device_get_sysnum(self.device) }; |
245 | |
246 | if ptr.is_null() { |
247 | return None; |
248 | } |
249 | |
250 | match str::from_utf8(unsafe { CStr::from_ptr(ptr) }.to_bytes()) { |
251 | Err(_) => None, |
252 | Ok(s) => FromStr::from_str(s).ok(), |
253 | } |
254 | } |
255 | |
256 | /// Returns the devtype name of the device. |
257 | pub fn devtype(&self) -> Option<&OsStr> { |
258 | unsafe { util::ptr_to_os_str(ffi::udev_device_get_devtype(self.device)) } |
259 | } |
260 | |
261 | /// Returns the name of the kernel driver attached to the device. |
262 | pub fn driver(&self) -> Option<&OsStr> { |
263 | unsafe { util::ptr_to_os_str(ffi::udev_device_get_driver(self.device)) } |
264 | } |
265 | |
266 | /// Retreives the value of a device property. |
267 | pub fn property_value<T: AsRef<OsStr>>(&self, property: T) -> Option<&OsStr> { |
268 | let prop = match util::os_str_to_cstring(property) { |
269 | Ok(s) => s, |
270 | Err(_) => return None, |
271 | }; |
272 | |
273 | unsafe { |
274 | util::ptr_to_os_str(ffi::udev_device_get_property_value( |
275 | self.device, |
276 | prop.as_ptr(), |
277 | )) |
278 | } |
279 | } |
280 | |
281 | /// Retreives the value of a device attribute. |
282 | pub fn attribute_value<T: AsRef<OsStr>>(&self, attribute: T) -> Option<&OsStr> { |
283 | let attr = match util::os_str_to_cstring(attribute) { |
284 | Ok(s) => s, |
285 | Err(_) => return None, |
286 | }; |
287 | |
288 | unsafe { |
289 | util::ptr_to_os_str(ffi::udev_device_get_sysattr_value( |
290 | self.device, |
291 | attr.as_ptr(), |
292 | )) |
293 | } |
294 | } |
295 | |
296 | /// Sets the value of a device attribute. |
297 | pub fn set_attribute_value<T: AsRef<OsStr>, U: AsRef<OsStr>>( |
298 | &mut self, |
299 | attribute: T, |
300 | value: U, |
301 | ) -> Result<()> { |
302 | let attribute = util::os_str_to_cstring(attribute)?; |
303 | let value = util::os_str_to_cstring(value)?; |
304 | |
305 | util::errno_to_result(unsafe { |
306 | ffi::udev_device_set_sysattr_value( |
307 | self.device, |
308 | attribute.as_ptr(), |
309 | value.as_ptr() as *mut c_char, |
310 | ) |
311 | }) |
312 | } |
313 | |
314 | /// Returns an iterator over the device's properties. |
315 | /// |
316 | /// ## Example |
317 | /// |
318 | /// This example prints out all of a device's properties: |
319 | /// |
320 | /// ```no_run |
321 | /// # use std::path::Path; |
322 | /// # let device = udev::Device::from_syspath(Path::new("/sys/devices/virtual/tty/tty0" )).unwrap(); |
323 | /// for property in device.properties() { |
324 | /// println!("{:?} = {:?}" , property.name(), property.value()); |
325 | /// } |
326 | /// ``` |
327 | pub fn properties(&self) -> Properties { |
328 | Properties { |
329 | entry: unsafe { ffi::udev_device_get_properties_list_entry(self.device) }, |
330 | phantom: PhantomData, |
331 | } |
332 | } |
333 | |
334 | /// Returns an iterator over the device's attributes. |
335 | /// |
336 | /// ## Example |
337 | /// |
338 | /// This example prints out all of a device's attributes: |
339 | /// |
340 | /// ```no_run |
341 | /// # use std::path::Path; |
342 | /// # let device = udev::Device::from_syspath(Path::new("/sys/devices/virtual/tty/tty0" )).unwrap(); |
343 | /// for attribute in device.attributes() { |
344 | /// println!("{:?} = {:?}" , attribute.name(), attribute.value()); |
345 | /// } |
346 | /// ``` |
347 | pub fn attributes(&self) -> Attributes { |
348 | Attributes { |
349 | entries: EntryList { |
350 | entry: unsafe { ffi::udev_device_get_sysattr_list_entry(self.device) }, |
351 | phantom: PhantomData, |
352 | }, |
353 | device: self, |
354 | } |
355 | } |
356 | |
357 | /// Returns the device action for the device. |
358 | pub fn action(&self) -> Option<&OsStr> { |
359 | unsafe { util::ptr_to_os_str(ffi::udev_device_get_action(self.device)) } |
360 | } |
361 | } |
362 | |
363 | impl<'a> Iterator for Attributes<'a> { |
364 | type Item = Entry<'a>; |
365 | |
366 | // The list of sysattr entries only contains the attribute names, with |
367 | // the values being empty. To get the value, each has to be queried. |
368 | fn next(&mut self) -> Option<Entry<'a>> { |
369 | match self.entries.next() { |
370 | Some(Entry { name: &OsStr, value: _ }) => Some(Entry { |
371 | name, |
372 | value: self.device.attribute_value(attribute:name), |
373 | }), |
374 | None => None, |
375 | } |
376 | } |
377 | |
378 | fn size_hint(&self) -> (usize, Option<usize>) { |
379 | (0, None) |
380 | } |
381 | } |
382 | |