1 | use std::str; |
2 | |
3 | use std::ffi::{CStr, CString, 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 | /// Permissible types of UNIX file I/O API device special file. |
25 | /// |
26 | /// See also [`from_devnum`][crate::Device::from_devnum]. |
27 | #[repr (u8)] |
28 | pub 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 | |
35 | impl std::fmt::Debug for Device { |
36 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
37 | f&mut DebugStruct<'_, '_>.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(name:"parent" , &self.parent()) |
50 | .finish() |
51 | } |
52 | } |
53 | |
54 | impl 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 | |
63 | impl 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" )] |
72 | unsafe impl Send for Device {} |
73 | #[cfg (feature = "sync" )] |
74 | unsafe impl Sync for Device {} |
75 | |
76 | as_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. |
79 | pub type Properties<'a> = EntryList<'a, Device>; |
80 | |
81 | /// A convenience alias for a list of attributes, bound to a device. |
82 | pub struct Attributes<'a> { |
83 | entries: EntryList<'a, Device>, |
84 | device: &'a Device, |
85 | } |
86 | |
87 | impl 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 | |
534 | impl<'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: &OsStr, value: _ }) => Some(Entry { |
542 | name, |
543 | value: self.device.attribute_value(attribute:name), |
544 | }), |
545 | None => None, |
546 | } |
547 | } |
548 | |
549 | fn size_hint(&self) -> (usize, Option<usize>) { |
550 | (0, None) |
551 | } |
552 | } |
553 | |