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}