diff options
Diffstat (limited to 'third_party/rust/authenticator/src/transport/linux/monitor.rs')
-rw-r--r-- | third_party/rust/authenticator/src/transport/linux/monitor.rs | 194 |
1 files changed, 194 insertions, 0 deletions
diff --git a/third_party/rust/authenticator/src/transport/linux/monitor.rs b/third_party/rust/authenticator/src/transport/linux/monitor.rs new file mode 100644 index 0000000000..ee88622de9 --- /dev/null +++ b/third_party/rust/authenticator/src/transport/linux/monitor.rs @@ -0,0 +1,194 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::transport::device_selector::DeviceSelectorEvent; +use libc::{c_int, c_short, c_ulong}; +use libudev::EventType; +use runloop::RunLoop; +use std::collections::HashMap; +use std::error::Error; +use std::io; +use std::os::unix::io::AsRawFd; +use std::path::PathBuf; +use std::sync::{mpsc::Sender, Arc}; + +const UDEV_SUBSYSTEM: &str = "hidraw"; +const POLLIN: c_short = 0x0001; +const POLL_TIMEOUT: c_int = 100; + +fn poll(fds: &mut Vec<::libc::pollfd>) -> io::Result<()> { + let nfds = fds.len() as c_ulong; + + let rv = unsafe { ::libc::poll((fds[..]).as_mut_ptr(), nfds, POLL_TIMEOUT) }; + + if rv < 0 { + Err(io::Error::from_raw_os_error(rv)) + } else { + Ok(()) + } +} + +pub struct Monitor<F> +where + F: Fn(PathBuf, Sender<DeviceSelectorEvent>, Sender<crate::StatusUpdate>, &dyn Fn() -> bool) + + Sync, +{ + runloops: HashMap<PathBuf, RunLoop>, + new_device_cb: Arc<F>, + selector_sender: Sender<DeviceSelectorEvent>, + status_sender: Sender<crate::StatusUpdate>, +} + +impl<F> Monitor<F> +where + F: Fn(PathBuf, Sender<DeviceSelectorEvent>, Sender<crate::StatusUpdate>, &dyn Fn() -> bool) + + Send + + Sync + + 'static, +{ + pub fn new( + new_device_cb: F, + selector_sender: Sender<DeviceSelectorEvent>, + status_sender: Sender<crate::StatusUpdate>, + ) -> Self { + Self { + runloops: HashMap::new(), + new_device_cb: Arc::new(new_device_cb), + selector_sender, + status_sender, + } + } + + pub fn run(&mut self, alive: &dyn Fn() -> bool) -> Result<(), Box<dyn Error>> { + let ctx = libudev::Context::new()?; + + let mut enumerator = libudev::Enumerator::new(&ctx)?; + enumerator.match_subsystem(UDEV_SUBSYSTEM)?; + + // Iterate all existing devices. + let paths: Vec<PathBuf> = enumerator + .scan_devices()? + .filter_map(|dev| dev.devnode().map(|p| p.to_owned())) + .collect(); + + // Add them all in one go to avoid race conditions in DeviceSelector + // (8 devices should be added, but the first returns already before all + // others are known to DeviceSelector) + self.selector_sender + .send(DeviceSelectorEvent::DevicesAdded(paths.clone()))?; + for path in paths { + self.add_device(path); + } + + let mut monitor = libudev::Monitor::new(&ctx)?; + monitor.match_subsystem(UDEV_SUBSYSTEM)?; + + // Start listening for new devices. + let mut socket = monitor.listen()?; + let mut fds = vec![::libc::pollfd { + fd: socket.as_raw_fd(), + events: POLLIN, + revents: 0, + }]; + + while alive() { + // Wait for new events, break on failure. + poll(&mut fds)?; + + if let Some(event) = socket.receive_event() { + self.process_event(&event); + } + } + + // Remove all tracked devices. + self.remove_all_devices(); + + Ok(()) + } + + fn process_event(&mut self, event: &libudev::Event) { + let path = event.device().devnode().map(|dn| dn.to_owned()); + + match (event.event_type(), path) { + (EventType::Add, Some(path)) => { + let _ = self + .selector_sender + .send(DeviceSelectorEvent::DevicesAdded(vec![path.clone()])); + self.add_device(path); + } + (EventType::Remove, Some(path)) => { + self.remove_device(&path); + } + _ => { /* ignore other types and failures */ } + } + } + + fn add_device(&mut self, path: PathBuf) { + let f = self.new_device_cb.clone(); + let key = path.clone(); + let selector_sender = self.selector_sender.clone(); + let status_sender = self.status_sender.clone(); + debug!("Adding device {}", path.to_string_lossy()); + + let runloop = RunLoop::new(move |alive| { + if alive() { + f(path, selector_sender, status_sender, alive); + } + }); + + if let Ok(runloop) = runloop { + self.runloops.insert(key, runloop); + } + } + + fn remove_device(&mut self, path: &PathBuf) { + let _ = self + .selector_sender + .send(DeviceSelectorEvent::DeviceRemoved(path.clone())); + + debug!("Removing device {}", path.to_string_lossy()); + if let Some(runloop) = self.runloops.remove(path) { + runloop.cancel(); + } + } + + fn remove_all_devices(&mut self) { + while !self.runloops.is_empty() { + let path = self.runloops.keys().next().unwrap().clone(); + self.remove_device(&path); + } + } +} + +pub fn get_property_linux(path: &PathBuf, prop_name: &str) -> io::Result<String> { + let ctx = libudev::Context::new()?; + + let mut enumerator = libudev::Enumerator::new(&ctx)?; + enumerator.match_subsystem(UDEV_SUBSYSTEM)?; + + // Iterate all existing devices, since we don't have a syspath + // and libudev-rs doesn't implement opening by devnode. + for dev in enumerator.scan_devices()? { + if dev.devnode().is_some() && dev.devnode().unwrap() == path { + debug!( + "get_property_linux Querying property {} from {}", + prop_name, + dev.syspath().display() + ); + + let value = dev + .attribute_value(prop_name) + .ok_or(io::ErrorKind::Other)? + .to_string_lossy(); + + debug!("get_property_linux Fetched Result, {}={}", prop_name, value); + return Ok(value.to_string()); + } + } + + Err(io::Error::new( + io::ErrorKind::Other, + "Unable to find device", + )) +} |