#[cfg(target_os = "linux")]
use glob::glob;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
use super::*;
use crate::profiler::SYSFS_USB_PREFIX;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UsbPath(PathBuf);
impl fmt::Display for UsbPath {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0.display())
}
}
impl AsRef<Path> for UsbPath {
fn as_ref(&self) -> &Path {
&self.0
}
}
impl From<PathBuf> for UsbPath {
fn from(path: PathBuf) -> Self {
Self(path)
}
}
impl From<&Path> for UsbPath {
fn from(path: &Path) -> Self {
Self(path.to_path_buf())
}
}
impl UsbPath {
pub fn new<P: AsRef<Path>>(path: P) -> Self {
Self(path.as_ref().to_path_buf())
}
pub fn path(&self) -> &Path {
&self.0
}
pub fn parent(&self) -> Option<&Path> {
let path_str = self.path().to_str()?;
let index = path_str.rfind('-').or_else(|| path_str.rfind("usb"))?;
let index = path_str[..index].rfind('/')?;
Some(Path::new(&path_str[..index]))
}
pub fn is_bus_controller(&self) -> bool {
self.sysfs_device()
.map(|f| f.starts_with("usb"))
.unwrap_or(false)
}
pub fn is_root_hub(&self) -> bool {
self.sysfs_trunk()
.map(|f| f.ends_with("-0"))
.unwrap_or(self.is_bus_controller())
}
pub fn bus(&self) -> Option<u8> {
self.port_path().map(|f| f.bus)
}
pub fn sysfs_trunk(&self) -> Option<&str> {
self.sysfs_name()
.and_then(|f| f.split_once('.').map(|f| f.0).or(Some(f)))
}
pub fn sysfs_device_path(&self) -> Option<&Path> {
let path_str = self.path().to_str().unwrap();
let index = path_str.rfind('-').or_else(|| path_str.rfind("usb"))?;
let end_index = path_str[index..]
.find('/')
.map(|f| f.saturating_add(index))
.unwrap_or(path_str.len());
Some(Path::new(&path_str[..end_index]))
}
fn sysfs_device_str(&self) -> Option<&str> {
self.sysfs_device_path()?
.file_name()
.and_then(|f| f.to_str())
}
pub fn sysfs_device(&self) -> Option<&str> {
if self.device_path().is_some() {
self.sysfs_device_str()
} else {
None
}
}
pub fn device_path(&self) -> Option<DevicePath> {
self.sysfs_device_str().and_then(|f| f.parse().ok())
}
pub fn sysfs_port_path(&self) -> Option<&Path> {
self.sysfs_device_path()
.and_then(|f| f.to_str())
.and_then(|f| f.split_once(':').map(|f| f.0).or(Some(f)))
.map(Path::new)
}
fn sysfs_port_str(&self) -> Option<&str> {
self.sysfs_port_path()
.and_then(|f| f.file_name().and_then(|f| f.to_str()))
}
pub fn sysfs_name(&self) -> Option<&str> {
if self.port_path().is_some() {
self.sysfs_port_str()
} else {
None
}
}
pub fn port_path(&self) -> Option<PortPath> {
self.sysfs_port_str().and_then(|f| f.parse().ok())
}
pub fn configuration(&self) -> Option<u8> {
self.device_path().and_then(|f| f.config)
}
pub fn interface(&self) -> Option<u8> {
self.device_path().and_then(|f| f.interface)
}
fn endpoint_path_str(&self) -> Option<&str> {
let path_str = self.path().to_str()?;
let index = path_str.rfind("ep_")?;
let end_index = path_str[index..]
.find('/')
.map(|f| f.saturating_add(index))
.unwrap_or(path_str.len());
Some(&path_str[index..end_index])
}
pub fn endpoint_path(&self) -> Option<EndpointPath> {
Some(EndpointPath::new_with_device_path(
self.device_path()?,
self.endpoint()?,
))
}
pub fn endpoint(&self) -> Option<u8> {
self.endpoint_path_str()
.and_then(|f| f.strip_prefix("ep_"))
.and_then(|f| f.parse().ok())
}
pub fn to_sysfs_path(&self) -> PathBuf {
if self.path().starts_with(SYSFS_USB_PREFIX) {
self.path().to_path_buf()
} else {
Path::new(SYSFS_USB_PREFIX).join(self.path())
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct PortPath {
bus: u8,
ports: Vec<u8>,
}
impl FromStr for PortPath {
type Err = Error;
fn from_str(s: &str) -> error::Result<Self> {
if let Some(s) = s.strip_prefix("usb") {
let num = s
.parse::<u8>()
.map_err(|_| Error::new(ErrorKind::Parsing, &format!("Invalid bus number: {s}")))?;
Ok(Self {
bus: num,
ports: vec![],
})
} else {
let mut parts = s.split(':').next().unwrap_or(s).split('-');
let bus = parts
.next()
.ok_or_else(|| Error::new(ErrorKind::Parsing, &format!("No bus number: {s}")))?
.parse()
.map_err(|_| Error::new(ErrorKind::Parsing, &format!("Invalid bus number: {s}")))?;
let ports = parts
.next()
.ok_or_else(|| Error::new(ErrorKind::Parsing, &format!("No port number: {s}")))?
.split('.')
.map(|p| p.parse())
.collect::<Result<Vec<_>, _>>()
.map_err(|_| {
Error::new(ErrorKind::Parsing, &format!("Invalid port number: {s}"))
})?;
Ok(Self { bus, ports })
}
}
}
impl fmt::Display for PortPath {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.ports.is_empty() {
if f.alternate() {
write!(f, "usb{}", self.bus)
} else {
write!(f, "{}-0", self.bus)
}
} else {
write!(f, "{}-{}", self.bus, self.ports.iter().format("."))
}
}
}
impl TryFrom<&Path> for PortPath {
type Error = Error;
fn try_from(s: &Path) -> error::Result<Self> {
s.file_name()
.and_then(|f| f.to_str())
.ok_or_else(|| Error::new(ErrorKind::InvalidPath, "Invalid path"))
.and_then(|f| f.parse())
}
}
impl TryFrom<&str> for PortPath {
type Error = Error;
fn try_from(s: &str) -> error::Result<Self> {
s.parse()
}
}
impl From<PortPath> for UsbPath {
fn from(p: PortPath) -> Self {
UsbPath::new(p.to_string())
}
}
impl From<&PortPath> for UsbPath {
fn from(p: &PortPath) -> Self {
UsbPath::new(p.to_string())
}
}
impl PortPath {
pub fn new(bus: u8, ports: Vec<u8>) -> Self {
Self { bus, ports }
}
pub fn bus(&self) -> u8 {
self.bus
}
pub fn ports(&self) -> &[u8] {
&self.ports
}
pub fn parent(&self) -> Option<Self> {
if self.ports.is_empty() {
None
} else {
Some(Self {
bus: self.bus,
ports: self.ports[..self.ports.len() - 1].to_vec(),
})
}
}
pub fn trunk(&self) -> Self {
if self.ports.is_empty() {
Self {
bus: self.bus,
ports: vec![],
}
} else {
Self {
bus: self.bus,
ports: vec![self.ports[0]],
}
}
}
pub fn depth(&self) -> usize {
self.ports.len()
}
pub fn is_root_hub(&self) -> bool {
self.ports.is_empty() || self.ports == [0]
}
}
pub type ConfigurationPath = (PortPath, u8);
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DevicePath {
port_path: PortPath,
config: Option<u8>,
interface: Option<u8>,
alt_setting: Option<u8>,
}
impl FromStr for DevicePath {
type Err = Error;
fn from_str(s: &str) -> error::Result<Self> {
let (p, ci) = s.split_once(':').unwrap_or((s, ""));
let port_path = p.parse()?;
let mut parts = ci.split('.');
match (
parts.next().and_then(|f| f.parse::<u8>().ok()),
parts.next().and_then(|f| f.parse::<u8>().ok()),
) {
(Some(config), Some(interface)) => Ok(Self {
port_path,
config: Some(config),
interface: Some(interface),
alt_setting: None,
}),
_ => Ok(Self {
port_path,
config: None,
interface: None,
alt_setting: None,
}),
}
}
}
impl fmt::Display for DevicePath {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.port_path)?;
if let (Some(config), Some(interface)) = (self.config, self.interface) {
write!(f, ":{config}")?;
write!(f, ".{interface}")?;
}
Ok(())
}
}
impl TryFrom<&Path> for DevicePath {
type Error = Error;
fn try_from(s: &Path) -> error::Result<Self> {
s.file_name()
.and_then(|f| f.to_str())
.ok_or_else(|| Error::new(ErrorKind::InvalidPath, "Invalid path"))
.and_then(|f| f.parse())
}
}
impl From<DevicePath> for PortPath {
fn from(d: DevicePath) -> Self {
d.port_path
}
}
impl From<PortPath> for DevicePath {
fn from(p: PortPath) -> Self {
Self {
port_path: p,
config: None,
interface: None,
alt_setting: None,
}
}
}
impl From<&DevicePath> for UsbPath {
fn from(d: &DevicePath) -> Self {
UsbPath::new(d.to_string())
}
}
impl From<DevicePath> for UsbPath {
fn from(d: DevicePath) -> Self {
UsbPath::new(d.to_string())
}
}
impl DevicePath {
pub fn new_with_port_path(
port_path: PortPath,
config: Option<u8>,
interface: Option<u8>,
alt_setting: Option<u8>,
) -> Self {
Self {
port_path,
config,
interface,
alt_setting,
}
}
pub fn new(
bus: u8,
ports: Vec<u8>,
config: Option<u8>,
interface: Option<u8>,
alt_setting: Option<u8>,
) -> Self {
Self {
port_path: PortPath::new(bus, ports),
config,
interface,
alt_setting,
}
}
pub fn port_path(&self) -> &PortPath {
&self.port_path
}
pub fn configuration(&self) -> Option<u8> {
self.config
}
pub fn interface(&self) -> Option<u8> {
self.interface
}
pub fn alt_setting(&self) -> u8 {
self.alt_setting.unwrap_or(0)
}
pub fn set_alt_setting(&mut self, alt: u8) {
self.alt_setting = Some(alt);
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct EndpointPath {
device_path: DevicePath,
endpoint: u8,
}
impl FromStr for EndpointPath {
type Err = Error;
fn from_str(s: &str) -> error::Result<Self> {
let mut parts = s.split("/ep_");
if let (Some(d), Some(e)) = (parts.next(), parts.next()) {
let device_path: DevicePath = d.parse()?;
let endpoint = e
.parse()
.map_err(|_| Error::new(ErrorKind::Parsing, &format!("Invalid endpoint: {e}")))?;
Ok(Self {
device_path,
endpoint,
})
} else {
Err(Error::new(
ErrorKind::Parsing,
&format!("Invalid endpoint path: {s}"),
))
}
}
}
impl TryFrom<&Path> for EndpointPath {
type Error = Error;
fn try_from(s: &Path) -> error::Result<Self> {
s.to_str()
.ok_or_else(|| Error::new(ErrorKind::InvalidPath, "Invalid path"))
.and_then(|f| f.parse())
}
}
impl fmt::Display for EndpointPath {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.device_path)?;
write!(f, "/ep_{}", self.endpoint)
}
}
impl From<EndpointPath> for UsbPath {
fn from(ep: EndpointPath) -> Self {
UsbPath::new(ep.device_path.to_string())
}
}
impl EndpointPath {
pub fn new_with_device_path(device_path: DevicePath, endpoint: u8) -> Self {
Self {
device_path,
endpoint,
}
}
pub fn new_with_port_path(
port_path: PortPath,
config: u8,
interface: u8,
alt_setting: u8,
endpoint: u8,
) -> Self {
Self {
device_path: DevicePath::new_with_port_path(
port_path,
Some(config),
Some(interface),
Some(alt_setting),
),
endpoint,
}
}
pub fn new(
bus: u8,
ports: Vec<u8>,
config: u8,
interface: u8,
alt_setting: u8,
endpoint: u8,
) -> Self {
Self {
device_path: DevicePath::new(
bus,
ports,
Some(config),
Some(interface),
Some(alt_setting),
),
endpoint,
}
}
pub fn device_path(&self) -> &DevicePath {
&self.device_path
}
pub fn endpoint(&self) -> u8 {
self.endpoint
}
pub fn endpoint_address(&self) -> EndpointAddress {
EndpointAddress::from(self.endpoint)
}
}
pub fn get_port_path(bus: u8, ports: &[u8]) -> String {
if ports.is_empty() {
format!("{bus:}-0")
} else {
format!("{:}-{}", bus, ports.iter().format("."))
}
}
pub fn get_parent_path(bus: u8, ports: &[u8]) -> Option<String> {
if ports.is_empty() {
None
} else {
Some(get_port_path(bus, &ports[..ports.len() - 1]))
}
}
pub fn get_trunk_path(bus: u8, ports: &[u8]) -> String {
if ports.is_empty() {
format!("{bus:}-0")
} else {
format!("{:}-{}", bus, ports[0])
}
}
pub fn get_interface_path(bus: u8, ports: &[u8], config: u8, interface: u8) -> String {
format!("{}:{}.{}", get_port_path(bus, ports), config, interface)
}
pub fn get_endpoint_path(
bus: u8,
ports: &[u8],
config: u8,
interface: u8,
endpoint: u8,
) -> PathBuf {
format!(
"{}/ep_{}",
get_interface_path(bus, ports, config, interface),
endpoint
)
.into()
}
pub fn get_dev_path(bus: u8, device_no: Option<u8>) -> PathBuf {
if let Some(devno) = device_no {
format!("/dev/bus/usb/{bus:03}/{devno:03}").into()
} else {
format!("/dev/bus/usb/{bus:03}/001").into()
}
}
pub fn get_sysfs_name(bus: u8, ports: &[u8]) -> String {
if ports.is_empty() {
format!("usb{bus}")
} else {
get_port_path(bus, ports)
}
}
pub fn get_serial_device_path(sysfs_path: &Path) -> Option<PathBuf> {
if !cfg!(target_os = "linux") {
return None;
}
let tty_dir = sysfs_path.join("tty");
if tty_dir.is_dir() {
std::fs::read_dir(tty_dir).ok()
} else {
None
}
.and_then(|mut entries| {
entries.next().and_then(|entry| {
entry.ok().map(|e| {
let dev_name = e.file_name().to_string_lossy().to_string();
std::path::Path::new("/dev").join(dev_name)
})
})
})
}
#[allow(unused_variables)]
pub fn get_block_device_path(sysfs_path: &Path) -> Option<Vec<PathBuf>> {
#[cfg(target_os = "linux")]
{
let glob_pattern = sysfs_path.join("host*/target*:*:*/*:*:*:*/block/*");
let paths = glob(glob_pattern.to_str()?)
.ok()?
.filter_map(|entry| {
entry.ok().and_then(|path| {
path.file_name()
.map(|name| std::path::Path::new("/dev").join(name))
})
})
.collect::<Vec<_>>();
if paths.is_empty() {
None
} else {
Some(paths)
}
}
#[cfg(not(target_os = "linux"))]
{
None
}
}
pub fn get_usb_mounts(block_path: &Path) -> error::Result<Option<Vec<(PathBuf, PathBuf)>>> {
let mut mounts = Vec::new();
let file = File::open("/proc/mounts")?;
let reader = BufReader::new(file);
for line in reader.lines() {
let line = line?;
let block_str = block_path.to_str().ok_or_else(|| {
Error::new(ErrorKind::InvalidPath, "Invalid block device path string")
})?;
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 2 {
continue; }
let device = parts[0];
let mount_point = parts[1];
if device == block_str {
mounts.push((PathBuf::from(device), PathBuf::from(mount_point)));
} else if device.starts_with(block_str) {
if let Some(num) = device.strip_prefix(block_str) {
if num.chars().next().is_some_and(|c| c.is_ascii_digit()) {
mounts.push((PathBuf::from(device), PathBuf::from(mount_point)));
}
}
}
}
if mounts.is_empty() {
Ok(None)
} else {
Ok(Some(mounts))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_port_path_parse() {
assert_eq!(
"1-1.3.4".parse::<PortPath>(),
Ok(PortPath {
bus: 1,
ports: vec![1, 3, 4]
})
);
assert_eq!(
"1-1.3".parse::<PortPath>(),
Ok(PortPath {
bus: 1,
ports: vec![1, 3]
})
);
assert_eq!(
"1-2".parse::<PortPath>(),
Ok(PortPath {
bus: 1,
ports: vec![2]
})
);
assert_eq!(
"1-0:1-0".parse::<PortPath>(),
Ok(PortPath {
bus: 1,
ports: vec![0]
})
);
assert_eq!(
"usb1".parse::<PortPath>(),
Ok(PortPath {
bus: 1,
ports: vec![]
})
);
}
#[test]
fn test_port_path_display() {
assert_eq!(
PortPath {
bus: 1,
ports: vec![1, 3, 4]
}
.to_string(),
"1-1.3.4"
);
assert_eq!(
PortPath {
bus: 1,
ports: vec![1, 3]
}
.to_string(),
"1-1.3"
);
assert_eq!(
PortPath {
bus: 1,
ports: vec![2]
}
.to_string(),
"1-2"
);
assert_eq!(
PortPath {
bus: 1,
ports: vec![0]
}
.to_string(),
"1-0"
);
assert_eq!(
PortPath {
bus: 1,
ports: vec![]
}
.to_string(),
"1-0"
);
}
}