1use std::str;
2
3use std::ffi::{CStr, OsStr};
4use std::io::Result;
5use std::marker::PhantomData;
6use std::path::Path;
7use std::ptr;
8use std::str::FromStr;
9
10use libc::{c_char, dev_t};
11
12use list::{Entry, EntryList};
13use Udev;
14use {ffi, util};
15
16use {AsRaw, FromRaw};
17
18/// A structure that provides access to sysfs/kernel devices.
19pub struct Device {
20 udev: Udev,
21 device: *mut ffi::udev_device,
22}
23
24impl 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
43impl 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
52impl Drop for Device {
53 fn drop(&mut self) {
54 unsafe {
55 ffi::udev_device_unref(self.device);
56 }
57 }
58}
59
60as_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.
63pub type Properties<'a> = EntryList<'a, Device>;
64
65/// A convenience alias for a list of attributes, bound to a device.
66pub struct Attributes<'a> {
67 entries: EntryList<'a, Device>,
68 device: &'a Device,
69}
70
71impl 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
363impl<'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