-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8fa83a4
commit b684798
Showing
4 changed files
with
219 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,147 @@ | ||
use std::{io::ErrorKind, task::Poll}; | ||
use log::{debug, error, warn}; | ||
use rustix::{ | ||
fd::{AsFd, OwnedFd}, | ||
net::{ | ||
bind, | ||
netlink::{self, SocketAddrNetlink}, | ||
recvfrom, socket_with, AddressFamily, RecvFlags, SocketAddrAny, SocketFlags, SocketType, | ||
}, | ||
}; | ||
use std::{io::ErrorKind, os::unix::prelude::BorrowedFd, path::Path, task::Poll}; | ||
|
||
use crate::{hotplug::HotplugEvent, Error}; | ||
|
||
pub(crate) struct LinuxHotplugWatch {} | ||
use super::{enumeration::probe_device, events::Async, SysfsPath}; | ||
|
||
const UDEV_MAGIC: &[u8; 12] = b"libudev\0\xfe\xed\xca\xfe"; | ||
const UDEV_MULTICAST_GROUP: u32 = 1 << 1; | ||
|
||
pub(crate) struct LinuxHotplugWatch { | ||
fd: Async<OwnedFd>, | ||
} | ||
|
||
impl LinuxHotplugWatch { | ||
pub(crate) fn new() -> Result<Self, Error> { | ||
Err(Error::new(ErrorKind::Unsupported, "Not implemented.")) | ||
let fd = socket_with( | ||
AddressFamily::NETLINK, | ||
SocketType::RAW, | ||
SocketFlags::CLOEXEC, | ||
Some(netlink::KOBJECT_UEVENT), | ||
)?; | ||
bind(&fd, &SocketAddrNetlink::new(0, UDEV_MULTICAST_GROUP))?; | ||
Ok(LinuxHotplugWatch { | ||
fd: Async::new(fd)?, | ||
}) | ||
} | ||
|
||
pub(crate) fn poll_next(&mut self, cx: &mut std::task::Context<'_>) -> Poll<HotplugEvent> { | ||
if let Some(event) = try_receive_event(self.fd.inner.as_fd()) { | ||
return Poll::Ready(event); | ||
} | ||
|
||
if let Err(e) = self.fd.register(cx.waker()) { | ||
log::error!("failed to register udev socket with epoll: {e}"); | ||
} | ||
|
||
Poll::Pending | ||
} | ||
} | ||
|
||
fn try_receive_event(fd: BorrowedFd) -> Option<HotplugEvent> { | ||
let mut buf = [0; 8192]; | ||
|
||
match recvfrom(fd, &mut buf, RecvFlags::DONTWAIT) { | ||
// udev messages will normally be sent to a multicast group, which only | ||
// root can send to. Reject unicast messages that may be from anywhere. | ||
Ok((size, Some(SocketAddrAny::Netlink(nl)))) if nl.groups() == UDEV_MULTICAST_GROUP => { | ||
parse_packet(&buf[..size]) | ||
} | ||
Ok((_, src)) => { | ||
warn!("udev netlink socket received message from {src:?}"); | ||
None | ||
} | ||
Err(e) if e.kind() == ErrorKind::WouldBlock => None, | ||
Err(e) => { | ||
error!("udev netlink socket recvfrom failed with {e}"); | ||
None | ||
} | ||
} | ||
} | ||
|
||
fn parse_packet(buf: &[u8]) -> Option<HotplugEvent> { | ||
if buf.len() < 24 { | ||
error!("packet too short: {buf:x?}"); | ||
return None; | ||
} | ||
|
||
if !buf.starts_with(UDEV_MAGIC) { | ||
error!("packet does not start with expected header: {buf:x?}"); | ||
return None; | ||
} | ||
|
||
let properties_off = u32::from_ne_bytes(buf[16..20].try_into().unwrap()) as usize; | ||
let properties_len = u32::from_ne_bytes(buf[20..24].try_into().unwrap()) as usize; | ||
let Some(properties_buf) = buf.get(properties_off..properties_off + properties_len) else { | ||
error!("properties offset={properties_off} length={properties_len} exceeds buffer length {len}", len = buf.len()); | ||
return None; | ||
}; | ||
|
||
let mut is_add = None; | ||
let mut busnum = None; | ||
let mut devnum = None; | ||
let mut devpath = None; | ||
|
||
for (k, v) in parse_properties(properties_buf) { | ||
debug!("uevent property {k} = {v}"); | ||
match k { | ||
"SUBSYSTEM" if v != "usb" => return None, | ||
"DEVTYPE" if v != "usb_device" => return None, | ||
"ACTION" => { | ||
is_add = Some(match v { | ||
"add" => true, | ||
"remove" => false, | ||
_ => return None, | ||
}); | ||
} | ||
"BUSNUM" => { | ||
busnum = v.parse::<u8>().ok(); | ||
} | ||
"DEVNUM" => { | ||
devnum = v.parse::<u8>().ok(); | ||
} | ||
"DEVPATH" => { | ||
devpath = Some(v); | ||
} | ||
_ => {} | ||
} | ||
} | ||
|
||
let is_add = is_add?; | ||
let busnum = busnum?; | ||
let devnum = devnum?; | ||
let devpath = devpath?; | ||
|
||
if is_add { | ||
let path = Path::new("/sys/").join(devpath.trim_start_matches('/')); | ||
match probe_device(SysfsPath(path.clone())) { | ||
Ok(d) => Some(HotplugEvent::Connected(d)), | ||
Err(e) => { | ||
error!("Failed to probe device {path:?}: {e}"); | ||
None | ||
} | ||
} | ||
} else { | ||
Some(HotplugEvent::Disconnected(crate::DeviceId( | ||
super::DeviceId { | ||
bus: busnum, | ||
addr: devnum, | ||
}, | ||
))) | ||
} | ||
} | ||
|
||
/// Split nul-separated key=value pairs | ||
fn parse_properties(buf: &[u8]) -> impl Iterator<Item = (&str, &str)> + '_ { | ||
buf.split(|b| b == &0) | ||
.filter_map(|entry| std::str::from_utf8(entry).ok()?.split_once('=')) | ||
} |