commit: | 646d90bdfcb93966c5b1e2f917b81cf6ea630d99 |
author: | Trevor Bentley |
committer: | Trevor Bentley |
date: | Mon Feb 20 17:48:37 2023 +0100 |
parents: |
diff --git a/.gitignore b/.gitignore line changes: +1/-0 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore
@@ -0,0 +1 @@ +/target
diff --git a/Cargo.lock b/Cargo.lock line changes: +508/-0 index 0000000..8e50f52 --- /dev/null +++ b/Cargo.lock
@@ -0,0 +1,508 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "bindgen" +version = "0.59.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "clap", + "env_logger", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "dbus" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" +dependencies = [ + "libc", + "libdbus-sys", + "winapi", +] + +[[package]] +name = "dbus-crossroads" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4c83437187544ba5142427746835061b330446ca8902eabd70e4afb8f76de0" +dependencies = [ + "dbus", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "libdbus-sys" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f8d7ae751e1cb825c840ae5e682f59b098cdfd213c350ac268b61449a5f58a0" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "proc-macro2" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "qemu-stormcrow" +version = "0.1.0" +dependencies = [ + "dbus", + "dbus-crossroads", + "mio", + "udev", + "virt", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "udev" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebdbbd670373442a12fe9ef7aeb53aec4147a5a27a00bbc3ab639f08f48191a" +dependencies = [ + "libc", + "libudev-sys", + "mio", + "pkg-config", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "virt" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df0697e60d61398cee42ff0ad350843f05ef84bc34df012653a7e6a3ef067bd" +dependencies = [ + "libc", + "virt-sys", +] + +[[package]] +name = "virt-sys" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88207efda613081369fcb3701283201f9b5745c8f9c09e737921ca0103f5873" +dependencies = [ + "bindgen", + "libc", + "pkg-config", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
diff --git a/Cargo.toml b/Cargo.toml line changes: +13/-0 index 0000000..7351219 --- /dev/null +++ b/Cargo.toml
@@ -0,0 +1,13 @@ +[package] +name = "qemu-stormcrow" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +dbus = "0.9.7" +dbus-crossroads = "0.5.2" +mio = {version = "0.8.5", features = ["os-ext"] } +virt = "0.2.12" +udev = {version = "0.7.0", features = ["mio08"] }
diff --git a/README.md b/README.md line changes: +37/-0 index 0000000..1caaeec --- /dev/null +++ b/README.md
@@ -0,0 +1,37 @@ +# qemu-stormcrow + +## WHAT + +A little utility to dynamically passthrough USB devices to running libvirt virtual machines. + +## WHY + +libvirt supports permanently mapping a USB device to a VM, or dynamically attaching a device once during runtime. It does not have very good support for temporarily passing through USB devices that are frequently reconnected. qemu-stormcrow is for that case. + +## HOW + +qemu-stormcrow should be launched as a daemon first. `cargo run` in a terminal, or write a systemd service, or spawn it in the background from a script, or whatever. It does not self-daemonize. + +A USB device with a (=Vendor ID=, =Product ID=) pair is registered for a running libvirt VM via D-Bus: + +```bash +$ dbus-send --type=method_call --print-reply --dest=com.stormcrow.device /device com.stormcrow.device.Add string:<VM> string:<VID> string:<PID> +``` + +qemu-stormcrow monitors the udev subsystem for attach/remove events of the VID/PID pair. When one is attached, qemu-stormcrow generates a libvirt hostdev XML snippet for the device and attaches it to the running VM. Likewise, it detaches the hostdev device when removed. + +When finished, the device can be unregistered. qemu-stormcrow will no longer monitor for such devices: + +```bash +$ dbus-send --type=method_call --print-reply --dest=com.stormcrow.device /device com.stormcrow.device.Remove string:<VM> string:<VID> string:<PID> +``` + +qemu-stormcrow can be shutdown via D-Bus as well: + +```bash +$ dbus-send --type=method_call --print-reply --dest=com.stormcrow.device /device com.stormcrow.device.Quit +``` + +## SHOULD I USE THIS? + +No. It's a hacky little script for personal use.
diff --git a/src/main.rs b/src/main.rs line changes: +291/-0 index 0000000..57ede47 --- /dev/null +++ b/src/main.rs
@@ -0,0 +1,291 @@ +use dbus::blocking::Connection as DbusConnection; +use dbus_crossroads::{Context, Crossroads}; +use mio::{Events, Interest, Poll, Token}; +use std::collections::{BTreeMap, HashSet}; +use std::error::Error; +use std::io::{self, Read}; +use std::path::PathBuf; +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::thread; +use std::time::Duration; +use udev::MonitorBuilder; +use virt::connect::Connect; +use virt::domain::Domain; + +#[derive(Debug)] +pub enum DbusCommand { + Add, + Remove, + Shutdown, +} + +#[derive(Eq, PartialEq, Hash, Clone, Debug)] +pub struct UsbFilter { + vid: Option<String>, + pid: Option<String>, +} + +struct DbusDevice { + sender: Sender<(DbusCommand, String, UsbFilter)>, +} + +// $ dbus-send --type=method_call --print-reply --dest=com.stormcrow.device /device com.stormcrow.device.Add string:<VM> string:<VID> string:<PID> +fn dbus_server(sender: Sender<(DbusCommand, String, UsbFilter)>) -> Result<(), Box<dyn Error>> { + let c = DbusConnection::new_session()?; + c.request_name("com.stormcrow.device", false, true, false)?; + let mut cr = Crossroads::new(); + let iface_token = cr.register("com.stormcrow.device", |b| { + b.method( + "Add", + ("vm", "vid", "pid"), + ("reply",), + move |_ctx: &mut Context, + dev: &mut DbusDevice, + (vm, vid, pid): (String, String, String)| { + println!("Incoming Add call for {}:{}!", vid, pid); + let filter = UsbFilter { + vid: match vid.len() == 4 { + true => Some(vid), + _ => None, + }, + pid: match pid.len() == 4 { + true => Some(pid), + _ => None, + }, + }; + dev.sender + .send((DbusCommand::Add, vm, filter)) + .expect("failed to transmit from dbus channel"); + let reply = "OK"; + Ok((reply,)) + }, + ); + b.method( + "Remove", + ("vm", "vid", "pid"), + ("reply",), + move |_ctx: &mut Context, + dev: &mut DbusDevice, + (vm, vid, pid): (String, String, String)| { + println!("Incoming Remove call for {}:{}!", vid, pid); + let filter = UsbFilter { + vid: match vid.len() == 4 { + true => Some(vid), + _ => None, + }, + pid: match pid.len() == 4 { + true => Some(pid), + _ => None, + }, + }; + dev.sender + .send((DbusCommand::Remove, vm, filter)) + .expect("failed to transmit from dbus channel"); + let reply = "OK"; + Ok((reply,)) + }, + ); + b.method( + "Quit", + (), + ("reply",), + move |_ctx: &mut Context, dev: &mut DbusDevice, (): ()| { + dev.sender + .send(( + DbusCommand::Shutdown, + "".into(), + UsbFilter { + vid: None, + pid: None, + }, + )) + .expect("failed to transmit from dbus channel"); + Ok(("BYE",)) + }, + ); + }); + + cr.insert("/device", &[iface_token], DbusDevice { sender }); + + // Serve clients forever. + cr.serve(&c)?; + Ok(()) +} + +fn usb_xml(vid: &str, pid: &str, bus: &str, dev: &str) -> String { + format!( + r" +<hostdev mode='subsystem' type='usb'> + <source> + <vendor id='0x{}'/> + <product id='0x{}'/> + <address bus='{}' device='{}'/> + </source> +</hostdev> +", + vid, pid, bus, dev + ) +} + +pub fn poll( + mut socket: udev::MonitorSocket, + receiver: Receiver<(DbusCommand, String, UsbFilter)>, +) -> io::Result<()> { + let mut poll = Poll::new()?; + let mut events = Events::with_capacity(1024); + + let mut filters = BTreeMap::<String, HashSet<UsbFilter>>::new(); + let mut sysdevs = BTreeMap::<PathBuf, UsbFilter>::new(); + let mut xmls = BTreeMap::<String, Vec<(PathBuf, String)>>::new(); + + let uri = "qemu:///system"; + println!("Attempting to connect to hypervisor: '{}'...", uri); + let mut conn = match Connect::open(uri) { + Ok(c) => c, + Err(e) => panic!("No connection to hypervisor: {}", e), + }; + + poll.registry().register( + &mut socket, + Token(0), + Interest::READABLE | Interest::WRITABLE, + )?; + + println!("Polling udev monitor..."); + 'event: loop { + poll.poll(&mut events, Some(Duration::from_millis(200)))?; + while let Ok(msg) = receiver.try_recv() { + match msg.0 { + DbusCommand::Shutdown => { + break 'event; + } + DbusCommand::Add => { + let vm = msg.1; + let filter = msg.2; + if !filters.contains_key(&vm) { + filters.insert(vm.clone(), HashSet::new()); + } + if let Some(usb_filters) = filters.get_mut(&vm) { + if !usb_filters.contains(&filter) { + println!("udev add: {:?}:{:?}", filter.vid, filter.pid); + usb_filters.insert(filter); + } + } + } + DbusCommand::Remove => { + let vm = msg.1; + let filter = msg.2; + if let Some(usb_filters) = filters.get_mut(&vm) { + if usb_filters.contains(&filter) { + println!("udev rem: {:?}:{:?}", filter.vid, filter.pid); + usb_filters.remove(&filter); + } + } + } + } + } + + for event in &events { + if event.token() == Token(0) && event.is_writable() { + socket.iter().for_each(|x| { + let syspath = x.device().syspath().to_owned(); + let mut vidpath = syspath.clone(); + let mut pidpath = syspath.clone(); + let mut buspath = syspath.clone(); + let mut devpath = syspath.clone(); + vidpath.push("idVendor"); + pidpath.push("idProduct"); + buspath.push("busnum"); + devpath.push("devnum"); + match x.event_type() { + udev::EventType::Add => { + let mut usb_vid = String::new(); + let mut usb_pid = String::new(); + let mut usb_bus = String::new(); + let mut usb_dev = String::new(); + let mut f = std::fs::File::open(vidpath).expect("couldn't open USB vendor"); + f.read_to_string(&mut usb_vid).expect("failed to read USB vendor"); + let mut f = std::fs::File::open(pidpath).expect("couldn't open USB product"); + f.read_to_string(&mut usb_pid).expect("failed to read USB vendor"); + let mut f = std::fs::File::open(buspath).expect("couldn't open USB bus"); + f.read_to_string(&mut usb_bus).expect("failed to read USB vendor"); + let mut f = std::fs::File::open(devpath).expect("couldn't open USB device"); + f.read_to_string(&mut usb_dev).expect("failed to read USB vendor"); + let usb_vid = usb_vid.trim(); + let usb_pid = usb_pid.trim(); + let usb_bus = usb_bus.trim(); + let usb_dev = usb_dev.trim(); + let usb_filter = UsbFilter {vid: Some(usb_vid.into()), pid: Some(usb_pid.into())}; + for (vm, vm_filter) in filters.iter() { + if vm_filter.contains(&usb_filter) { + println!("Adding syspath: {} for vm {} [VID:{} PID:{}]", syspath.display(), vm, usb_vid, usb_pid); + sysdevs.insert(syspath.clone(), usb_filter.clone()); + if let Ok(domain) = Domain::lookup_by_name(&conn, vm) { + let xml = usb_xml(usb_vid, usb_pid, usb_bus, usb_dev); + domain.attach_device(&xml).expect("failed to attach USB XML!"); + if !xmls.contains_key(vm) { + xmls.insert(vm.to_owned(), Vec::new()); + } + if let Some(vm_xmls) = xmls.get_mut(vm) { + vm_xmls.push((syspath.clone(), xml)); + } + } + } + } + }, + udev::EventType::Remove => { + match sysdevs.contains_key(&syspath) { + true => { + println!("Removing syspath: {}", syspath.display()); + sysdevs.remove(&syspath); + for (vm, vm_xmls) in xmls.iter_mut() { + for (vm_syspath, xml_str) in vm_xmls.iter() { + if vm_syspath == &syspath { + if let Ok(domain) = Domain::lookup_by_name(&conn, vm) { + if let Err(e) = domain.detach_device(xml_str) { + println!("WARNING: failed to hot-unplug from domain {}: {}", vm, e); + } + } + } + } + vm_xmls.retain(|i| i.0 != syspath); + } + }, + false => { + }, + } + }, + _ => {}, + } + }); + } + } + } + + println!("Shutting down by request."); + if let Err(e) = conn.close() { + panic!("Failed to disconnect from hypervisor: {}", e); + } + Ok(()) +} + +fn main() { + println!("Starting qemu-stormcrow."); + + println!("Starting dbus monitor..."); + let (sender, receiver) = channel::<(DbusCommand, String, UsbFilter)>(); + thread::spawn(move || { + dbus_server(sender).expect("failed to launch dbus server"); + }); + + println!("Making udev monitor..."); + let socket = MonitorBuilder::new() + .expect("failed to create new udev monitor") + .match_subsystem_devtype("usb", "usb_device") + .expect("failed to create usb matcher") + .listen() + .expect("failed to register udev monitor"); + + poll(socket, receiver).expect("failed to poll udev monitor"); + println!("Done!"); +}