diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c17be3d..f99c21e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,9 +21,13 @@ jobs: fail-fast: false matrix: job: - - { os: ubuntu-latest, target: aarch64-unknown-linux-gnu, use-cross: true, feature-flags: "--all-features" } + # only default as don't generate docs or hwdb + - { os: ubuntu-latest, target: aarch64-unknown-linux-gnu, use-cross: true, feature-flags: "" } + # all ok as udev ignored on non-linux - { os: ubuntu-latest, target: x86_64-pc-windows-gnu, use-cross: true, feature-flags: "--all-features" } - - { os: ubuntu-latest, target: x86_64-unknown-linux-gnu, use-cross: false, feature-flags: "--all-features" } + # specificy to avoid udevlib + - { os: ubuntu-latest, target: x86_64-unknown-linux-gnu, use-cross: false, feature-flags: "-F=cli_generate" } + # all ok as udev ignored on non-linux - { os: macos-latest, target: universal-apple-darwin, use-cross: false, feature-flags: "--all-features" } steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2502008..3048e71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,13 @@ ## [Unreleased] -- Working on full dumps of device descriptors ([#15](https://github.com/tuna-f1sh/cyme/issues/15)) +### Addded + +- Full dumps of device descriptors for matching `--lsusb --verbose` ([#15](https://github.com/tuna-f1sh/cyme/issues/15)) + +### Changed + +- Replace [udev-rs](https://github.com/Smithay/udev-rs) and indirectly libudev-sys with Rust native [udev](https://github.com/cr8t/udev); libudev dependency (and system requirement) is now optional but can be used with `--no-default-features -F=udevlib`. ([#19](https://github.com/tuna-f1sh/cyme/pull/19)) ## [1.6.1] - 2024-13-06 diff --git a/Cargo.lock b/Cargo.lock index c645e23..d32dad2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,6 +75,15 @@ dependencies = [ "serde_json", ] +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -105,6 +114,12 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cc" version = "1.0.83" @@ -216,9 +231,15 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + [[package]] name = "cyme" -version = "1.6.0" +version = "1.7.0" dependencies = [ "assert-json-diff", "clap", @@ -241,6 +262,7 @@ dependencies = [ "strum_macros", "terminal_size 0.2.6", "udev", + "udevrs", "usb-ids", ] @@ -348,12 +370,40 @@ dependencies = [ "wasi", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "spin", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.4.1" @@ -484,7 +534,8 @@ dependencies = [ [[package]] name = "libudev-sys" version = "0.1.4" -source = "git+https://github.com/Emilgardis/libudev-sys/?branch=fix-cross-compilation#808a604d27d0afc1305bbcc5d27c6c083c99dfa4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" dependencies = [ "libc", "pkg-config", @@ -514,6 +565,16 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.20" @@ -532,6 +593,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -706,6 +778,15 @@ dependencies = [ "libusb1-sys", ] +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.37.27" @@ -745,6 +826,18 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "serde" version = "1.0.193" @@ -822,6 +915,21 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strsim" version = "0.10.0" @@ -952,6 +1060,20 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "udevrs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdc959ba33f1deec76a55dd1fc7f6265b404dfb6155dc3c14f21551fa60e1a09" +dependencies = [ + "bitflags 2.4.1", + "glob", + "heapless", + "libc", + "log", + "nix", +] + [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/Cargo.toml b/Cargo.toml index 2d0fbda..22305ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ description = "List system USB buses and devices; a modern cross-platform `lsusb repository = "https://github.com/tuna-f1sh/cyme" readme = "README.md" license = "GPL-3.0-or-later" -version = "1.6.1" +version = "1.7.0" edition = "2021" keywords = ["usb", "lsusb", "system_profiler", "macos", "libusb"] categories = ["command-line-utilities"] @@ -32,32 +32,34 @@ terminal_size = "0.2.5" strum = "0.24.1" strum_macros = "0.24.3" -[patch.crates-io] -libudev-sys = { git = "https://github.com/Emilgardis/libudev-sys/", branch = "fix-cross-compilation" } - [dev-dependencies] diff = "0.1" assert-json-diff = "2.0.2" [target.x86_64-unknown-linux-gnu.dependencies] -udev = { version = "^0.8.0", optional = true } +udevrs = { version = "^0.3.0", optional = true } +udevlib = { package = "udev", version = "^0.8.0", optional = true } rusb = "0.9.4" [target.arm-unknown-linux-gnueabihf.dependencies] -udev = { version = "^0.8.0", optional = true } +udevrs = { version = "^0.3.0", optional = true } +udevlib = { package = "udev", version = "^0.8.0", optional = true } rusb = "0.9.4" [target.aarch64-unknown-linux-gnu.dependencies] -udev = { version = "^0.8.0", optional = true } +udevrs = { version = "^0.3.0", optional = true } +udevlib = { package = "udev", version = "^0.8.0", optional = true } rusb = "0.9.4" [features] libusb = ["dep:rusb"] -udev = ["dep:udev"] -udev_hwdb = ["udev/hwdb"] +udev = ["libusb", "dep:udevrs"] +udev_hwdb = ["libusb", "udevlib?/hwdb"] +# libudev C binding +udevlib = ["libusb", "dep:udevlib"] usb_test = [] cli_generate = ["dep:clap_complete", "dep:clap_mangen"] # for generating man and completions -default = ["libusb"] +default = ["libusb", "udev"] [[bin]] name = "cyme" diff --git a/README.md b/README.md index b856151..9afa5fa 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,6 @@ The name comes from the technical term for the type of blossom on a Apple tree: ## Requirements * Linux/Windows and pre-compiled targets require [libusb 1.0.0](https://libusb.info): `brew install libusb`, `sudo apt install libusb-1.0-0-dev` or one's package manager of choice. -* Linux pre-compiled and `--features udev`/`--features udev_hwdb` requires 'libudev-dev': `sudo apt install libudev-dev` or one's package manager of choice. For pre-compiled binaries, see the [releases](https://github.com/tuna-f1sh/cyme/releases). @@ -70,9 +69,12 @@ More package managers to come/package distribution, please feel free to create a ## Linux udev -To obtain device and interface drivers being used on Linux like `lsusb`, one must install 'libudev-dev' via a package manager and the `--features udev` feature when building. To lookup USB IDs from the udev hwdb as well (like `lsusb`) use `--features udev_hwdb`. Without hwdb, `cyme` will use the 'usb-ids' crate, which is the same source as the hwdb binary data but the bundled hwdb may differ due to customisations or last update ('usb-ids' will be most up to date). +> [!NOTE] +> Only supported on Linux targets. -Only supported on Linux targets. +To obtain device and interface drivers being used on Linux like `lsusb`, one can use the `--features udev` feature when building - it's a default feature. The feature uses the Rust crate [udevrs](https://crates.io/crates/udevrs) to obtain the information. To use the C FFI libudev library, use `--no-default-features --features udevlib` which will use the 'libudev' crate. Note that this will require 'libudev-dev' to be installed on the host machine. + +To lookup USB IDs from the udev hwdb as well (like `lsusb`) use `--features udev_hwdb`. Without hwdb, `cyme` will use the 'usb-ids' crate, which is the same source as the hwdb binary data but the bundled hwdb may differ due to customisations or last update ('usb-ids' will be most up to date). ## Alias `lsusb` diff --git a/src/lib.rs b/src/lib.rs index 0c0851a..e3704ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,6 +47,9 @@ pub mod system_profiler; pub mod types; #[cfg(all(target_os = "linux", feature = "udev"))] pub mod udev; +#[cfg(all(all(target_os = "linux", feature = "udevlib"), not(feature = "udev")))] +#[path = "udev_ffi.rs"] +pub mod udev; pub mod usb; /// Set cyme module and binary log level diff --git a/src/udev.rs b/src/udev.rs index 2fdfe84..cfd704c 100644 --- a/src/udev.rs +++ b/src/udev.rs @@ -1,6 +1,5 @@ //! Utilities to get device information using udev - only supported on Linux. Requires 'udev' feature. -use std::path::Path; -use udev as udevlib; +use udevrs::{udev_new, UdevDevice, UdevHwdb}; use crate::error::{Error, ErrorKind}; @@ -24,7 +23,7 @@ pub struct UdevInfo { /// ``` pub fn get_udev_info(port_path: &str) -> Result { let path: String = format!("/sys/bus/usb/devices/{}", port_path); - let device = udevlib::Device::from_syspath(Path::new(&path)).map_err(|e| { + let mut device = UdevDevice::new_from_syspath(udev_new(), &path).map_err(|e| { Error::new( ErrorKind::Udev, &format!( @@ -36,10 +35,8 @@ pub fn get_udev_info(port_path: &str) -> Result { Ok({ UdevInfo { - driver: device - .driver() - .map(|s| s.to_str().unwrap_or("").to_string()), - syspath: device.syspath().to_str().map(|s| s.to_string()), + driver: device.get_driver().map(|s| s.trim().to_string()), + syspath: Some(device.syspath().trim().to_string()), } }) } @@ -53,7 +50,7 @@ pub fn get_udev_info(port_path: &str) -> Result { /// ``` pub fn get_udev_driver_name(port_path: &str) -> Result, Error> { let path: String = format!("/sys/bus/usb/devices/{}", port_path); - let device = udevlib::Device::from_syspath(Path::new(&path)).map_err(|e| { + let mut device = UdevDevice::new_from_syspath(udev_new(), &path).map_err(|e| { Error::new( ErrorKind::Udev, &format!( @@ -63,9 +60,7 @@ pub fn get_udev_driver_name(port_path: &str) -> Result, Error> { ) })?; - Ok(device - .driver() - .map(|s| s.to_str().unwrap_or("").to_string())) + Ok(device.get_driver().map(|s| s.trim().to_string())) } /// Lookup the syspath for a device given the `port_path`. @@ -77,7 +72,7 @@ pub fn get_udev_driver_name(port_path: &str) -> Result, Error> { /// ``` pub fn get_udev_syspath(port_path: &str) -> Result, Error> { let path: String = format!("/sys/bus/usb/devices/{}", port_path); - let device = udevlib::Device::from_syspath(Path::new(&path)).map_err(|e| { + let device = UdevDevice::new_from_syspath(udev_new(), &path).map_err(|e| { Error::new( ErrorKind::Udev, &format!( @@ -87,7 +82,7 @@ pub fn get_udev_syspath(port_path: &str) -> Result, Error> { ) })?; - Ok(device.syspath().to_str().map(|s| s.to_string())) + Ok(Some(device.syspath().trim().to_string())) } /// Lookup a udev attribute given the `port_path` and `attribute`. @@ -104,12 +99,12 @@ pub fn get_udev_syspath(port_path: &str) -> Result, Error> { /// let interface_class = get_udev_attribute("1-0:1.0", "bInterfaceClass").unwrap(); /// assert_eq!(interface_class, Some("09".into())); /// ``` -pub fn get_udev_attribute + std::fmt::Display>( +pub fn get_udev_attribute + std::fmt::Display + Into>( port_path: &str, attribute: T, ) -> Result, Error> { let path: String = format!("/sys/bus/usb/devices/{}", port_path); - let device = udevlib::Device::from_syspath(Path::new(&path)).map_err(|e| { + let mut device = UdevDevice::new_from_syspath(udev_new(), &path).map_err(|e| { Error::new( ErrorKind::Udev, &format!( @@ -120,16 +115,14 @@ pub fn get_udev_attribute + std::fmt::Display>( })?; Ok(device - .attribute_value(attribute) - .map(|s| s.to_str().unwrap_or("").to_string())) + .get_sysattr_value(&attribute.into()) + .map(|s| s.trim().to_string())) } -/// udev hwdb lookup functions -/// -/// Protected by the `udev_hwdb` feature because 'libudev-sys' excludes hwdb ffi bindings if native udev does not support hwdb -#[cfg(feature = "udev_hwdb")] +/// Utilities to get device information using udev hwdb - only supported on Linux. Requires 'udev' feature. pub mod hwdb { use super::*; + /// Lookup an entry in the udev hwdb given the `modalias` and `key`. /// /// Should act like https://github.com/gregkh/usbutils/blob/master/names.c#L115 @@ -138,26 +131,24 @@ pub mod hwdb { /// use cyme::udev; /// /// let modalias = "usb:v1D6Bp0001"; - /// let vendor = hwdb::get(&modalias, "ID_VENDOR_FROM_DATABASE").unwrap(); + /// let vendor = udev::hwdb::get(&modalias, "ID_VENDOR_FROM_DATABASE").unwrap(); /// /// assert_eq!(vendor, Some("Linux Foundation".into())); /// - /// let modalias = "usb:v*p*d*dc03dsc01dp01*"; - /// let vendor = hwdb::get(&modalias, "ID_USB_PROTOCOL_FROM_DATABASE").unwrap(); + /// let modalias = "usb:v1366p0101"; + /// let vendor = udev::hwdb::get(&modalias, "ID_MODEL_FROM_DATABASE").unwrap(); /// - /// assert_eq!(vendor, Some("Keyboard".into())); + /// assert_eq!(vendor, Some("J-Link PLUS".into())); /// ``` pub fn get(modalias: &str, key: &'static str) -> Result, Error> { - let hwdb = udevlib::Hwdb::new().map_err(|e| { + let mut hwdb = UdevHwdb::new(udev_new()).map_err(|e| { Error::new( ErrorKind::Udev, &format!("Failed to get hwdb: Error({})", e), ) })?; - Ok(hwdb - .query_one(&modalias.to_string(), &key.to_string()) - .map(|s| s.to_str().unwrap_or("").to_string())) + Ok(udevrs::udev_hwdb_query_one(&mut hwdb, modalias, key).map(|s| s.trim().to_string())) } } @@ -171,7 +162,7 @@ mod tests { fn test_udev_info() { let udevi = get_udev_info("1-0:1.0").unwrap(); assert_eq!(udevi.driver, Some("hub".into())); - assert!(udevi.syspath.unwrap().contains("usb1/1-0:1.0")); + assert!(udevi.syspath.unwrap().contains("1-0:1.0")); } /// Tests can lookup bInterfaceClass of the root hub, which is always 09 diff --git a/src/udev_ffi.rs b/src/udev_ffi.rs new file mode 100644 index 0000000..befd265 --- /dev/null +++ b/src/udev_ffi.rs @@ -0,0 +1,184 @@ +//! Utilities to get device information using udev libudev FFI - only supported on Linux. Requires 'udev_ffi' feature. +use std::path::Path; +use udevlib; + +use crate::error::{Error, ErrorKind}; + +/// Contains data returned by [`get_udev_info()`]. +#[derive(Debug, Clone, PartialEq, Default)] +pub struct UdevInfo { + /// The driver name for the device + pub driver: Option, + /// The syspath for the device + pub syspath: Option, +} + +/// Lookup the driver and syspath for a device given the `port_path`. Returns [`UdevInfo`] containing both. +/// +/// ```no_run +/// use cyme::udev::get_udev_info; +/// +/// let udevi = get_udev_info("1-0:1.0").unwrap(); +/// assert_eq!(udevi.driver, Some("hub".into())); +/// assert_eq!(udevi.syspath.unwrap().contains("usb1/1-0:1.0"), true); +/// ``` +pub fn get_udev_info(port_path: &str) -> Result { + let path: String = format!("/sys/bus/usb/devices/{}", port_path); + let device = udevlib::Device::from_syspath(Path::new(&path)).map_err(|e| { + Error::new( + ErrorKind::Udev, + &format!( + "Failed to get udev info for device at {}: Error({})", + path, e + ), + ) + })?; + + Ok({ + UdevInfo { + driver: device + .driver() + .map(|s| s.to_str().unwrap_or("").to_string()), + syspath: device.syspath().to_str().map(|s| s.to_string()), + } + }) +} + +/// Lookup the driver name for a device given the `port_path`. +/// +/// ```no_run +/// use cyme::udev::get_udev_driver_name; +/// let driver = get_udev_driver_name("1-0:1.0").unwrap(); +/// assert_eq!(driver, Some("hub".into())); +/// ``` +pub fn get_udev_driver_name(port_path: &str) -> Result, Error> { + let path: String = format!("/sys/bus/usb/devices/{}", port_path); + let device = udevlib::Device::from_syspath(Path::new(&path)).map_err(|e| { + Error::new( + ErrorKind::Udev, + &format!( + "Failed to get udev info for device at {}: Error({})", + path, e + ), + ) + })?; + + Ok(device + .driver() + .map(|s| s.to_str().unwrap_or("").to_string())) +} + +/// Lookup the syspath for a device given the `port_path`. +/// +/// ```no_run +/// use cyme::udev::get_udev_syspath; +/// let syspath = get_udev_syspath("1-0:1.0").unwrap(); +/// assert_eq!(syspath.unwrap().contains("usb1/1-0:1.0"), true); +/// ``` +pub fn get_udev_syspath(port_path: &str) -> Result, Error> { + let path: String = format!("/sys/bus/usb/devices/{}", port_path); + let device = udevlib::Device::from_syspath(Path::new(&path)).map_err(|e| { + Error::new( + ErrorKind::Udev, + &format!( + "Failed to get udev info for device at {}: Error({})", + path, e + ), + ) + })?; + + Ok(device.syspath().to_str().map(|s| s.to_string())) +} + +/// Lookup a udev attribute given the `port_path` and `attribute`. +/// +/// This only works on Linux and not all devices have all attributes. +/// These attributes are generally readable by all users. +/// +/// NOTE: In general you should read from sysfs directly as it does not +/// depend on the udev feature. See `get_sysfs_string()` in lsusb.rs +/// +/// ```no_run +/// use cyme::udev::get_udev_attribute; +/// +/// let interface_class = get_udev_attribute("1-0:1.0", "bInterfaceClass").unwrap(); +/// assert_eq!(interface_class, Some("09".into())); +/// ``` +pub fn get_udev_attribute + std::fmt::Display>( + port_path: &str, + attribute: T, +) -> Result, Error> { + let path: String = format!("/sys/bus/usb/devices/{}", port_path); + let device = udevlib::Device::from_syspath(Path::new(&path)).map_err(|e| { + Error::new( + ErrorKind::Udev, + &format!( + "Failed to get udev attribute {} for device at {}: Error({})", + attribute, path, e + ), + ) + })?; + + Ok(device + .attribute_value(attribute) + .map(|s| s.to_str().unwrap_or("").to_string())) +} + +/// udev hwdb lookup functions +/// +/// Protected by the `udev_hwdb` feature because 'libudev-sys' excludes hwdb ffi bindings if native udev does not support hwdb +#[cfg(feature = "udev_hwdb")] +pub mod hwdb { + use super::*; + /// Lookup an entry in the udev hwdb given the `modalias` and `key`. + /// + /// Should act like https://github.com/gregkh/usbutils/blob/master/names.c#L115 + /// + /// ``` + /// use cyme::udev; + /// + /// let modalias = "usb:v1D6Bp0001"; + /// let vendor = udev::hwdb::get(&modalias, "ID_VENDOR_FROM_DATABASE").unwrap(); + /// + /// assert_eq!(vendor, Some("Linux Foundation".into())); + /// + /// let modalias = "usb:v*p*d*dc03dsc01dp01*"; + /// let vendor = udev::hwdb::get(&modalias, "ID_USB_PROTOCOL_FROM_DATABASE").unwrap(); + /// + /// assert_eq!(vendor, Some("Keyboard".into())); + /// ``` + pub fn get(modalias: &str, key: &'static str) -> Result, Error> { + let hwdb = udevlib::Hwdb::new().map_err(|e| { + Error::new( + ErrorKind::Udev, + &format!("Failed to get hwdb: Error({})", e), + ) + })?; + + Ok(hwdb + .query_one(&modalias.to_string(), &key.to_string()) + .map(|s| s.to_str().unwrap_or("").to_string())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Tests can obtain driver and syspath for root_hub on bus 1 - only do if we have USB + #[cfg_attr(not(feature = "usb_test"), ignore)] + #[test] + fn test_udev_info() { + let udevi = get_udev_info("1-0:1.0").unwrap(); + assert_eq!(udevi.driver, Some("hub".into())); + assert!(udevi.syspath.unwrap().contains("usb1/1-0:1.0")); + } + + /// Tests can lookup bInterfaceClass of the root hub, which is always 09 + #[cfg_attr(not(feature = "usb_test"), ignore)] + #[test] + fn test_udev_attribute() { + let interface_class = get_udev_attribute("1-0:1.0", "bInterfaceClass").unwrap(); + assert_eq!(interface_class, Some("09".into())); + } +}