Skip to content

Commit

Permalink
Introduce hardware DB querying with udev::hwdb (Smithay#21)
Browse files Browse the repository at this point in the history
This adds support for `udev`'s "hwdb" (hardware database) APIs. It
introduces the `hwdb` module and `Hwdb` structure for querying the
hardware database using modaliases.

Additionally, the `list` module is introduced. `list` contains a
generic wrapper for `udev`'s `udev_list_entry` and is used by
`hwdb::Hwdb`to return query results.

Other APIs that currently use their own specialized wrappers for
`udev_list_entry` can be rewritten to use `list::List`, although this
changeset doesn't do so to minimize churn.
  • Loading branch information
woodruffw authored Jan 3, 2021
1 parent d8c3809 commit f46abbc
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 0 deletions.
113 changes: 113 additions & 0 deletions src/hwdb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use std::ffi::{CString, OsStr};
use std::io::Result;
use std::marker::PhantomData;
use std::os::unix::ffi::OsStrExt;

use libc::c_char;

use ffi;
use list::List;
use FromRaw;

/// Rust wrapper for the `udev_hwdb` struct, which provides access to `udev`'s
/// hardware database API.
///
/// Like the `udev` struct, `udev_hwdb` is refcounted and automatically managed
/// by the Rust wrapper.
pub struct Hwdb {
hwdb: *mut ffi::udev_hwdb,
}

impl Clone for Hwdb {
fn clone(&self) -> Self {
unsafe { Self::from_raw(ffi::udev_hwdb_ref(self.hwdb)) }
}
}

impl Drop for Hwdb {
fn drop(&mut self) {
unsafe { ffi::udev_hwdb_unref(self.hwdb) };
}
}

as_ffi!(Hwdb, hwdb, ffi::udev_hwdb, ffi::udev_hwdb_ref);

impl Hwdb {
/// Creates a new Hwdb context.
pub fn new() -> Result<Self> {
// NOTE: udev_hwdb_new states that its first parameter is unused.
// However, older versions of udev check it against NULL, so we can't just pass an
// empty pointer in. Instead, we pass in a garbage pointer.
let junk: *mut ffi::udev = 0x41414141_41414141 as *mut ffi::udev;
let ptr = try_alloc!(unsafe { ffi::udev_hwdb_new(junk) });
Ok(unsafe { Self::from_raw(ptr) })
}

/// Queries the hardware database with the given `modalias` query,
/// returning an iterator over each matching entry.
pub fn query<S: AsRef<OsStr>>(&self, modalias: S) -> List<Hwdb> {
// NOTE: This expect can fail if someone passes a string that contains an internal NUL.
let modalias = CString::new(modalias.as_ref().as_bytes())
.expect("query() called with malformed modalias string");
List {
entry: unsafe {
ffi::udev_hwdb_get_properties_list_entry(
self.hwdb,
modalias.as_ptr() as *const c_char,
0,
)
},
phantom: PhantomData,
}
}

/// Returns the first entry value with the given name, or `None` if no result exists.
pub fn query_one<'a, S: AsRef<OsStr>>(&'a self, modalias: S, name: S) -> Option<&'a OsStr> {
self.query(modalias)
.find(|e| e.name == name.as_ref())
.map(|e| e.value)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_query() {
let hwdb = Hwdb::new().unwrap();
// Query the hwdb for a device that should always be known:
// the Linux Foundation's USB 1.1. root hub
let results: Vec<_> = hwdb.query("usb:v1D6Bp0001").collect();

assert_eq!(results.len(), 2);

// We expect an ID_VENDOR_FROM_DATABASE and an ID_MODEL_FROM_DATABASE with corresponding
// values; no order is specified by udev.

assert!(results
.iter()
.find(|e| e.name == "ID_VENDOR_FROM_DATABASE")
.is_some());
assert!(results
.iter()
.find(|e| e.name == "ID_MODEL_FROM_DATABASE")
.is_some());

assert!(results
.iter()
.find(|e| e.value == "Linux Foundation")
.is_some());
assert!(results.iter().find(|e| e.value == "1.1 root hub").is_some());
}

#[test]
fn test_query_one() {
let hwdb = Hwdb::new().unwrap();
let value = hwdb
.query_one("usb:v1D6Bp0001", "ID_MODEL_FROM_DATABASE")
.unwrap();

assert_eq!(value, "1.1 root hub");
}
}
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ extern crate mio;

pub use device::{Attribute, Attributes, Device, Properties, Property};
pub use enumerator::{Devices, Enumerator};
pub use hwdb::Hwdb;
pub use list::List;
pub use monitor::{Builder as MonitorBuilder, Event, EventType, Socket as MonitorSocket};
pub use udev::Udev;

Expand Down Expand Up @@ -185,6 +187,8 @@ macro_rules! from_raw_with_context {

mod device;
mod enumerator;
mod hwdb;
mod list;
mod monitor;
mod udev;
mod util;
58 changes: 58 additions & 0 deletions src/list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use std::ffi::OsStr;
use std::marker::PhantomData;

use ffi;
use util;

/// Rust wrapper for the `udev_list_entry` struct, which provides sequential
/// access to an associative list of string names and values.
///
/// Each `List<T>` is parametrized on the Rust wrapper type that owns its
/// underlying data. For example, `List<Hwdb>` indicates a list owned by
/// some open handle to the `udev` hardware database.
pub struct List<'a, T: 'a> {
pub(crate) entry: *mut ffi::udev_list_entry,
pub(crate) phantom: PhantomData<&'a T>,
}

impl<'a, T> Iterator for List<'a, T> {
type Item = Entry<'a>;

fn next(&mut self) -> Option<Entry<'a>> {
if self.entry.is_null() {
None
} else {
let name =
unsafe { util::ptr_to_os_str_unchecked(ffi::udev_list_entry_get_name(self.entry)) };
let value = unsafe {
util::ptr_to_os_str_unchecked(ffi::udev_list_entry_get_value(self.entry))
};

self.entry = unsafe { ffi::udev_list_entry_get_next(self.entry) };

Some(Entry { name, value })
}
}

fn size_hint(&self) -> (usize, Option<usize>) {
(0, None)
}
}

/// Rust wrapper for each entry in `List`, each of which contains a name and a value.
pub struct Entry<'a> {
pub(crate) name: &'a OsStr,
pub(crate) value: &'a OsStr,
}

impl<'a> Entry<'a> {
/// Returns the entry name.
pub fn name(&self) -> &OsStr {
self.name
}

/// Returns the entry value.
pub fn value(&self) -> &OsStr {
self.value
}
}

0 comments on commit f46abbc

Please sign in to comment.