#![deny(
clippy::all,
clippy::cargo,
clippy::nursery,
clippy::must_use_candidate
)]
#![allow(clippy::multiple_crate_versions)]
#![deny(missing_debug_implementations)]
#![deny(rustdoc::all)]
use std::{
os::unix::net::UnixStream,
path::{Path, PathBuf},
};
use anyhow::{anyhow, Context, Result};
use clap::{ArgAction, Parser};
use nusb::MaybeFuture;
use usbvfiod::hotplug_protocol::{
command::Command, device_paths::resolve_path, response::Response,
};
fn main() -> Result<()> {
let args = Cli::parse();
if let Some(path) = args.attach {
attach(path.as_path(), args.socket.as_path())?;
} else if let Some(vec) = args.detach {
let bus = vec[0];
let dev = vec[1];
detach(bus, dev, args.socket.as_path())?;
} else if args.list {
list_attached(args.socket.as_path())?;
}
Ok(())
}
fn attach(device_path: &Path, socket_path: &Path) -> Result<()> {
let (bus, dev, device_path) = resolve_path(device_path)
.with_context(|| format!("Failed to resolve device path {device_path:?}"))?;
println!("Requesting attachment of device {bus:03}:{dev:03}");
let open_file = |err_msg: &str| {
std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(&device_path)
.with_context(|| err_msg.to_string())
};
let file = open_file("Failed to open USB device file")?;
let device = nusb::Device::from_fd(file.into())
.wait()
.context("Failed to open nusb device")?;
device.reset().wait().context("Failed to reset device")?;
let file = open_file("Failed to open USB device file after device reset")?;
let command = Command::Attach {
bus,
device: dev,
fd: file,
};
let mut socket = UnixStream::connect(socket_path).context("Failed to open socket")?;
command
.send_over_socket(&socket)
.context("Failed to send attach command over the socket")?;
let response = Response::receive_from_socket(&mut socket)
.context("Failed to receive response over the socket")?;
println!("{response:?}");
Ok(())
}
fn detach(bus: u8, dev: u8, socket_path: &Path) -> Result<()> {
println!("Requesting detach of device {bus:03}:{dev:03}");
let command = Command::Detach { bus, device: dev };
let mut socket = UnixStream::connect(socket_path).context("Failed to open socket")?;
command
.send_over_socket(&socket)
.context("Failed to send detach command over the socket")?;
let response = Response::receive_from_socket(&mut socket)
.context("Failed to receive response over the socket")?;
println!("{response:?}");
Ok(())
}
fn list_attached(socket_path: &Path) -> Result<()> {
let mut socket = UnixStream::connect(socket_path).context("Failed to open socket")?;
Command::List
.send_over_socket(&socket)
.context("Failed to send list command over socket")?;
let response = Response::receive_from_socket(&mut socket)
.context("Failed to receive response over the socket")?;
if response != Response::ListFollowing {
return Err(anyhow!(
"Expected the response {:?} but got {:?}",
Response::ListFollowing,
response
));
}
let device_list = response.receive_devices_list(&mut socket)?;
match device_list.len() {
0 => println!("No attached devices"),
1 => {
println!("One attached device:");
println!("{:03}:{:03}", device_list[0].0, device_list[0].1);
}
count => {
println!("{count} attached devices:");
for (bus, dev) in device_list {
println!("{bus:03}:{dev:03}");
}
}
}
Ok(())
}
#[derive(Parser, Debug)]
#[command(
name = env!("CARGO_PKG_NAME"),
version = env!("CARGO_PKG_VERSION"),
author = env!("CARGO_PKG_AUTHORS"),
about = env!("CARGO_PKG_DESCRIPTION"),
long_about = None
)]
struct Cli {
#[arg(long, value_name = "PATH")]
socket: PathBuf,
#[arg(
long,
value_name = "PATH",
conflicts_with = "detach",
conflicts_with = "list"
)]
attach: Option<PathBuf>,
#[arg(long, num_args = 2, conflicts_with = "attach", conflicts_with = "list")]
detach: Option<Vec<u8>>,
#[arg(long, action = ArgAction::SetTrue, conflicts_with = "attach", conflicts_with = "detach")]
list: bool,
}