summaryrefslogtreecommitdiffstats
path: root/third_party/rust/authenticator/src/transport/linux/monitor.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/authenticator/src/transport/linux/monitor.rs')
-rw-r--r--third_party/rust/authenticator/src/transport/linux/monitor.rs194
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",
+ ))
+}