udev/
device.rs

1use std::str;
2
3use std::ffi::{CStr, CString, 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
24/// Permissible types of UNIX file I/O API device special file.
25///
26/// See also [`from_devnum`][crate::Device::from_devnum].
27#[repr(u8)]
28pub enum DeviceType {
29    /// UNIX character-style file IO semantics.
30    Character = b'c',
31    /// UNIX block-style file IO semantics.
32    Block = b'b',
33}
34
35impl std::fmt::Debug for Device {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        f.debug_struct("Device")
38            .field("initialized", &self.is_initialized())
39            .field("device_major_minor_number", &self.devnum())
40            .field("system_path", &self.syspath())
41            .field("device_path", &self.devpath())
42            .field("device_node", &self.devnode())
43            .field("subsystem_name", &self.subsystem())
44            .field("system_name", &self.sysname())
45            .field("instance_number", &self.sysnum())
46            .field("device_type", &self.devtype())
47            .field("driver", &self.driver())
48            .field("action", &self.action())
49            .field("parent", &self.parent())
50            .finish()
51    }
52}
53
54impl Clone for Device {
55    fn clone(&self) -> Self {
56        Self {
57            udev: self.udev.clone(),
58            device: unsafe { ffi::udev_device_ref(self.device) },
59        }
60    }
61}
62
63impl Drop for Device {
64    fn drop(&mut self) {
65        unsafe {
66            ffi::udev_device_unref(self.device);
67        }
68    }
69}
70
71#[cfg(feature = "send")]
72unsafe impl Send for Device {}
73#[cfg(feature = "sync")]
74unsafe impl Sync for Device {}
75
76as_ffi_with_context!(Device, device, ffi::udev_device, ffi::udev_device_ref);
77
78/// A convenience alias for a list of properties, bound to a device.
79pub type Properties<'a> = EntryList<'a, Device>;
80
81/// A convenience alias for a list of attributes, bound to a device.
82pub struct Attributes<'a> {
83    entries: EntryList<'a, Device>,
84    device: &'a Device,
85}
86
87impl Device {
88    /// Creates a device for a given syspath.
89    ///
90    /// The `syspath` parameter should be a path to the device file within the `sysfs` file system,
91    /// e.g., `/sys/devices/virtual/tty/tty0`.
92    pub fn from_syspath(syspath: &Path) -> Result<Self> {
93        // Create a new Udev context for this device
94        // It would be more efficient to allow callers to create just one context and use multiple
95        // devices, however that would be an API-breaking change.
96        //
97        // When devices are enumerated using an `Enumerator`, it will use
98        // `from_syspath_with_context` which can reuse the existing `Udev` context to avoid this
99        // extra overhead.
100        let udev = Udev::new()?;
101
102        Self::from_syspath_with_context(udev, syspath)
103    }
104
105    /// Creates a device for a given syspath, using an existing `Udev` instance rather than
106    /// creating one automatically.
107    ///
108    /// The `syspath` parameter should be a path to the device file within the `sysfs` file system,
109    /// e.g., `/sys/devices/virtual/tty/tty0`.
110    pub fn from_syspath_with_context(udev: Udev, syspath: &Path) -> Result<Self> {
111        let syspath = util::os_str_to_cstring(syspath)?;
112
113        let ptr = try_alloc!(unsafe {
114            ffi::udev_device_new_from_syspath(udev.as_raw(), syspath.as_ptr())
115        });
116
117        Ok(Self::from_raw(udev, ptr))
118    }
119
120    /// Create new udev device, and fill in information from the sys device
121    /// and the udev database entry.
122    ///
123    /// The device is looked up by the `subsystem` and `sysname` string of the device, like "mem" / "zero", or "block" / "sda".
124    pub fn from_subsystem_sysname(subsystem: String, sysname: String) -> Result<Self> {
125        let subsystem = CString::new(subsystem.as_bytes())
126            .ok()
127            .ok_or(std::io::Error::from_raw_os_error(libc::EINVAL))?;
128
129        let sysname = CString::new(sysname.as_bytes())
130            .ok()
131            .ok_or(std::io::Error::from_raw_os_error(libc::EINVAL))?;
132
133        let udev = Udev::new()?;
134
135        let ptr = try_alloc!(unsafe {
136            ffi::udev_device_new_from_subsystem_sysname(
137                udev.as_raw(),
138                subsystem.as_ptr(),
139                sysname.as_ptr(),
140            )
141        });
142
143        Ok(Self::from_raw(udev, ptr))
144    }
145
146    /// Create new udev device, and fill in information from the sys device
147    /// and the udev database entry, using an existing `Udev` instance rather than
148    /// creating a new one.
149    ///
150    /// The device is looked up by the `subsystem` and `sysname` string of the device, like "mem" / "zero", or "block" / "sda".
151    pub fn from_subsystem_sysname_with_context(
152        udev: Udev,
153        subsystem: String,
154        sysname: String,
155    ) -> Result<Self> {
156        let subsystem = CString::new(subsystem.as_bytes())
157            .ok()
158            .ok_or(std::io::Error::from_raw_os_error(libc::EINVAL))?;
159
160        let sysname = CString::new(sysname.as_bytes())
161            .ok()
162            .ok_or(std::io::Error::from_raw_os_error(libc::EINVAL))?;
163
164        let ptr = try_alloc!(unsafe {
165            ffi::udev_device_new_from_subsystem_sysname(
166                udev.as_raw(),
167                subsystem.as_ptr(),
168                sysname.as_ptr(),
169            )
170        });
171
172        Ok(Self::from_raw(udev, ptr))
173    }
174
175    /// Creates a rust udev `Device` for a given UNIX device "special file" type and number.
176    ///
177    /// The `dev_type` parameter indicates which of the historical UNIX file-like I/O paradigms the
178    /// device permits, and is either [`DeviceType::Character`] or [`DeviceType::Block`].
179    ///
180    /// n.b. This function follows the naming used by the underlying `libudev` function. As with
181    /// the underlying function, there is **no** **direct** **correspondence** between this
182    /// function's `dev_type` parameter and string values returned by [`devtype`][Self::devtype].
183    /// i.e. They represent different underlying concepts within the OS kernel.
184    ///
185    /// The `devnum` parameter is of type [`libc::dev_t`][libc::dev_t] which encodes the historical
186    /// UNIX major and minor device numbers (see below).
187    ///
188    /// Typically both parameters would be determined at run-time by calling one of the `stat`
189    /// family of system calls (or Rust std library functions which utilise them) on a filesystem
190    /// "special file" inode (e.g. `/dev/null`) or (more commonly) on a symbolic link to such a
191    /// file which was created by the `udevd` system daemon such as those under `/dev/disk/`.
192    ///
193    /// ```
194    /// use std::{env, fs, os::linux::fs::MetadataExt};
195    /// use udev::DeviceType;
196    ///
197    /// fn main() -> std::io::Result<()> {
198    ///     let args: Vec<String> = env::args().collect();
199    ///     # // Examples are automatically run as tests: provide dummy args for cargo test.
200    ///     # let args: Vec<String> = vec!("testname".into(), "/dev/null".into());
201    ///     let path = args.get(1).expect("No filename given");
202    ///     let metadata = fs::metadata(path).unwrap_or_else(|_| panic!("Can't open file: {}", path));
203    ///     let devtype = match metadata.st_mode() & libc::S_IFMT {
204    ///         libc::S_IFCHR => Some(DeviceType::Character),
205    ///         libc::S_IFBLK => Some(DeviceType::Block),
206    ///         _ => None,
207    ///     }.expect("Not a character or block special file");
208    ///     let ud = udev::Device::from_devnum(devtype, metadata.st_rdev())
209    ///         .expect("Couldn't construct udev from supplied path");
210    ///     println!("syspath of {} is {:?}", path, ud.syspath());
211    ///     let dn = ud.devnum();
212    ///     println!("devnum: {}", dn.unwrap());
213    ///     Ok(())
214    /// }
215    /// ```
216    /// The user should be aware that a given device may change its major and/or minor number
217    /// across reboots, when the hardware attached to the device is subject to hot-plug events, or
218    /// for a variety of other reasons.
219    ///
220    /// The `udevd` system daemon (or equivalent) is configured to dynamically create filesystem
221    /// symbolic links (examples of which can be seen under e.g. `/dev/disk/by-id/` on most Linux
222    /// systems), the purpose of which is to provide a predictable and persistent means of
223    /// identifying devices which themselves have a persistent state or identity.
224    ///
225    /// Code similar to the sample presented above may be used to obtain a [`udev::Device`][Self]
226    /// corresponding to the filesystem path of the UNIX file I/O style device node or symbolic
227    /// link.
228    ///
229    /// Historical UNIX systems statically allocated their internal data structures which were
230    /// associated with devices that exposed a "file-like" user-space API (e.g. `/dev/null`). A
231    /// device could be uniquely and persistently identified by combining its type (either
232    /// "character" or "block"), with its major and minor device numbers.
233    ///
234    /// In the underlying OS kernel, a major number might be allocated to a single device driver
235    /// such as a SCSI disk controller, and that device driver would allocate the minor device
236    /// number (e.g. `4` might have represented the 4th SCSI device addressable by a particular
237    /// SCSI host adapter). The `mknod` system utility would be used to create friendly filesystem
238    /// paths in the filesystem, which corresponded with these attributes, and file permissions
239    /// would be managed with utilities such as `chown` and `chmod` etc. and the numbers would not
240    /// change between system reboots.
241    ///
242    /// As has been noted, modern UNIX-like operating systems dynamically allocate devices. To
243    /// provide backward compatibility with existing user-space APIs, the concept of major/minor
244    /// devices being associated with file system "special file" inodes has been retained.
245    ///
246    /// For udev devices which present a UNIX file I/O style interface (i.e. via `/dev/` paths),
247    /// the Linux `udevadm` utility currently reports devices belonging to the `"block"` subsystem
248    /// to be of type "block", and all other file I/O style udev devices to be of type "character".
249    ///
250    /// Those needing to compose or decompose values of type `dev_t` should refer to
251    /// [`libc::major`], [`libc::minor`], [`libc::makedev`] and equivalent functionality from
252    /// higher-level rust crates.
253    pub fn from_devnum(dev_type: self::DeviceType, devnum: dev_t) -> Result<Self> {
254        let udev = Udev::new()?;
255
256        Self::from_devnum_with_context(udev, dev_type, devnum)
257    }
258
259    /// Creates a rust udev `Device` for a given UNIX device "special file" type and number. Uses
260    /// an existing [`Udev`] instance rather than creating one automatically.
261    ///
262    /// See [`from_devnum`][Self::from_devnum] for detailed usage.
263    pub fn from_devnum_with_context(
264        udev: Udev,
265        dev_type: self::DeviceType,
266        devnum: dev_t,
267    ) -> Result<Self> {
268        let ptr = try_alloc!(unsafe {
269            ffi::udev_device_new_from_devnum(udev.as_raw(), dev_type as c_char, devnum)
270        });
271
272        Ok(Self::from_raw(udev, ptr))
273    }
274
275    /// Creates a rust `Device` given an already created libudev `ffi::udev_device*` and a
276    /// corresponding `Udev` instance from which the device was created.
277    ///
278    /// This guarantees that the `Udev` will live longer than the corresponding `Device`
279    pub(crate) fn from_raw(udev: Udev, ptr: *mut ffi::udev_device) -> Self {
280        Self { udev, device: ptr }
281    }
282
283    /// Checks whether the device has already been handled by udev.
284    ///
285    /// When a new device is connected to the system, udev initializes the device by setting
286    /// permissions, renaming network devices, and possibly other initialization routines. This
287    /// method returns `true` if udev has performed all of its work to initialize this device.
288    ///
289    /// This method only applies to devices with device nodes or network interfaces. All other
290    /// devices return `true` by default.
291    pub fn is_initialized(&self) -> bool {
292        unsafe { ffi::udev_device_get_is_initialized(self.device) > 0 }
293    }
294
295    /// Gets the device's major/minor number.
296    pub fn devnum(&self) -> Option<dev_t> {
297        match unsafe { ffi::udev_device_get_devnum(self.device) } {
298            0 => None,
299            n => Some(n),
300        }
301    }
302
303    /// Returns the syspath of the device.
304    ///
305    /// The path is an absolute path and includes the sys mount point. For example, the syspath for
306    /// `tty0` could be `/sys/devices/virtual/tty/tty0`, which includes the sys mount point,
307    /// `/sys`.
308    pub fn syspath(&self) -> &Path {
309        Path::new(unsafe {
310            util::ptr_to_os_str_unchecked(ffi::udev_device_get_syspath(self.device))
311        })
312    }
313
314    /// Returns the kernel devpath value of the device.
315    ///
316    /// The path does not contain the sys mount point, but does start with a `/`. For example, the
317    /// devpath for `tty0` could be `/devices/virtual/tty/tty0`.
318    pub fn devpath(&self) -> &OsStr {
319        unsafe { util::ptr_to_os_str_unchecked(ffi::udev_device_get_devpath(self.device)) }
320    }
321
322    /// Returns the path to the device node belonging to the device.
323    ///
324    /// The path is an absolute path and starts with the device directory. For example, the device
325    /// node for `tty0` could be `/dev/tty0`.
326    pub fn devnode(&self) -> Option<&Path> {
327        unsafe { util::ptr_to_os_str(ffi::udev_device_get_devnode(self.device)) }
328            .map(|path| Path::new(path))
329    }
330
331    /// Returns the parent of the device.
332    pub fn parent(&self) -> Option<Self> {
333        let ptr = unsafe { ffi::udev_device_get_parent(self.device) };
334
335        if ptr.is_null() {
336            return None;
337        }
338
339        Some(Self::from_raw(self.udev.clone(), unsafe {
340            ffi::udev_device_ref(ptr)
341        }))
342    }
343
344    /// Returns the parent of the device with the matching subsystem and devtype if any.
345    pub fn parent_with_subsystem<T: AsRef<OsStr>>(&self, subsystem: T) -> Result<Option<Self>> {
346        let subsystem = util::os_str_to_cstring(subsystem)?;
347        let ptr = unsafe {
348            ffi::udev_device_get_parent_with_subsystem_devtype(
349                self.device,
350                subsystem.as_ptr(),
351                ptr::null(),
352            )
353        };
354
355        if ptr.is_null() {
356            return Ok(None);
357        }
358
359        Ok(Some(Self::from_raw(self.udev.clone(), unsafe {
360            ffi::udev_device_ref(ptr)
361        })))
362    }
363
364    /// Returns the parent of the device with the matching subsystem and devtype if any.
365    pub fn parent_with_subsystem_devtype<T: AsRef<OsStr>, U: AsRef<OsStr>>(
366        &self,
367        subsystem: T,
368        devtype: U,
369    ) -> Result<Option<Self>> {
370        let subsystem = util::os_str_to_cstring(subsystem)?;
371        let devtype = util::os_str_to_cstring(devtype)?;
372        let ptr = unsafe {
373            ffi::udev_device_get_parent_with_subsystem_devtype(
374                self.device,
375                subsystem.as_ptr(),
376                devtype.as_ptr(),
377            )
378        };
379
380        if ptr.is_null() {
381            return Ok(None);
382        }
383
384        Ok(Some(Self::from_raw(self.udev.clone(), unsafe {
385            ffi::udev_device_ref(ptr)
386        })))
387    }
388
389    /// Returns the subsystem name of the device.
390    ///
391    /// The subsystem name is a string that indicates which kernel subsystem the device belongs to.
392    /// Examples of subsystem names are `tty`, `vtconsole`, `block`, `scsi`, and `net`.
393    pub fn subsystem(&self) -> Option<&OsStr> {
394        unsafe { util::ptr_to_os_str(ffi::udev_device_get_subsystem(self.device)) }
395    }
396
397    /// Returns the kernel device name for the device.
398    ///
399    /// The sysname is a string that differentiates the device from others in the same subsystem.
400    /// For example, `tty0` is the sysname for a TTY device that differentiates it from others,
401    /// such as `tty1`.
402    pub fn sysname(&self) -> &OsStr {
403        unsafe { util::ptr_to_os_str_unchecked(ffi::udev_device_get_sysname(self.device)) }
404    }
405
406    /// Returns the instance number of the device.
407    ///
408    /// The instance number is used to differentiate many devices of the same type. For example,
409    /// `/dev/tty0` and `/dev/tty1` are both TTY devices but have instance numbers of 0 and 1,
410    /// respectively.
411    ///
412    /// Some devices don't have instance numbers, such as `/dev/console`, in which case the method
413    /// returns `None`.
414    pub fn sysnum(&self) -> Option<usize> {
415        let ptr = unsafe { ffi::udev_device_get_sysnum(self.device) };
416
417        if ptr.is_null() {
418            return None;
419        }
420
421        match str::from_utf8(unsafe { CStr::from_ptr(ptr) }.to_bytes()) {
422            Err(_) => None,
423            Ok(s) => FromStr::from_str(s).ok(),
424        }
425    }
426
427    /// Returns the devtype name of the device (if any), for example "disk".
428    pub fn devtype(&self) -> Option<&OsStr> {
429        unsafe { util::ptr_to_os_str(ffi::udev_device_get_devtype(self.device)) }
430    }
431
432    /// Returns the name of the kernel driver attached to the device.
433    pub fn driver(&self) -> Option<&OsStr> {
434        unsafe { util::ptr_to_os_str(ffi::udev_device_get_driver(self.device)) }
435    }
436
437    /// Retrieves the value of a device property.
438    pub fn property_value<T: AsRef<OsStr>>(&self, property: T) -> Option<&OsStr> {
439        let prop = match util::os_str_to_cstring(property) {
440            Ok(s) => s,
441            Err(_) => return None,
442        };
443
444        unsafe {
445            util::ptr_to_os_str(ffi::udev_device_get_property_value(
446                self.device,
447                prop.as_ptr(),
448            ))
449        }
450    }
451
452    /// Retrieves the value of a device attribute.
453    pub fn attribute_value<T: AsRef<OsStr>>(&self, attribute: T) -> Option<&OsStr> {
454        let attr = match util::os_str_to_cstring(attribute) {
455            Ok(s) => s,
456            Err(_) => return None,
457        };
458
459        unsafe {
460            util::ptr_to_os_str(ffi::udev_device_get_sysattr_value(
461                self.device,
462                attr.as_ptr(),
463            ))
464        }
465    }
466
467    /// Sets the value of a device attribute.
468    pub fn set_attribute_value<T: AsRef<OsStr>, U: AsRef<OsStr>>(
469        &mut self,
470        attribute: T,
471        value: U,
472    ) -> Result<()> {
473        let attribute = util::os_str_to_cstring(attribute)?;
474        let value = util::os_str_to_cstring(value)?;
475
476        util::errno_to_result(unsafe {
477            ffi::udev_device_set_sysattr_value(
478                self.device,
479                attribute.as_ptr(),
480                value.as_ptr() as *mut c_char,
481            )
482        })
483    }
484
485    /// Returns an iterator over the device's properties.
486    ///
487    /// ## Example
488    ///
489    /// This example prints out all of a device's properties:
490    ///
491    /// ```no_run
492    /// # use std::path::Path;
493    /// # let device = udev::Device::from_syspath(Path::new("/sys/devices/virtual/tty/tty0")).unwrap();
494    /// for property in device.properties() {
495    ///     println!("{:?} = {:?}", property.name(), property.value());
496    /// }
497    /// ```
498    pub fn properties(&self) -> Properties {
499        Properties {
500            entry: unsafe { ffi::udev_device_get_properties_list_entry(self.device) },
501            phantom: PhantomData,
502        }
503    }
504
505    /// Returns an iterator over the device's attributes.
506    ///
507    /// ## Example
508    ///
509    /// This example prints out all of a device's attributes:
510    ///
511    /// ```no_run
512    /// # use std::path::Path;
513    /// # let device = udev::Device::from_syspath(Path::new("/sys/devices/virtual/tty/tty0")).unwrap();
514    /// for attribute in device.attributes() {
515    ///     println!("{:?} = {:?}", attribute.name(), attribute.value());
516    /// }
517    /// ```
518    pub fn attributes(&self) -> Attributes {
519        Attributes {
520            entries: EntryList {
521                entry: unsafe { ffi::udev_device_get_sysattr_list_entry(self.device) },
522                phantom: PhantomData,
523            },
524            device: self,
525        }
526    }
527
528    /// Returns the device action for the device.
529    pub fn action(&self) -> Option<&OsStr> {
530        unsafe { util::ptr_to_os_str(ffi::udev_device_get_action(self.device)) }
531    }
532}
533
534impl<'a> Iterator for Attributes<'a> {
535    type Item = Entry<'a>;
536
537    // The list of sysattr entries only contains the attribute names, with
538    // the values being empty. To get the value, each has to be queried.
539    fn next(&mut self) -> Option<Entry<'a>> {
540        match self.entries.next() {
541            Some(Entry { name, value: _ }) => Some(Entry {
542                name,
543                value: self.device.attribute_value(name),
544            }),
545            None => None,
546        }
547    }
548
549    fn size_hint(&self) -> (usize, Option<usize>) {
550        (0, None)
551    }
552}