use clap::{Parser, Subcommand}; use std::{path::PathBuf, str::FromStr}; /// VM controller for `AirgapOS` #[derive(Parser, Clone, Debug)] pub struct App { // global options go here #[arg(long, global = true, default_value = "/var/run/netvm.pid")] pub lockfile: PathBuf, // #[command(subcommand)] pub subcommand: Commands, } #[derive(Subcommand, Clone, Debug)] pub enum Commands { /// Start a headless VM in the background. Start { #[arg(long, default_value = "1G")] memory: String, }, /// Stop a headless VM. Stop, /// Open a VM in the foreground with a serial terminal. Shell, /// Get the hostname and uptime of a running VM. Status, /// Attach a USB device to a running VM. Attach { /// The device to attach. device: DeviceIdentifier, }, /// Push a file to a currently running VM. Push { /// The local path to push. local_path: PathBuf, /// The remote path to push to. remote_path: PathBuf, }, /// Pull a file from a currently running VM. Pull { /// The remote path to pull. remote_path: PathBuf, /// The local path to pull to. local_path: PathBuf, }, /// Run a command in a currently running VM. Run { /// The command to run. command: String, /// Arguments to pass to the running command. args: Vec, }, } /// An attachable USB device identifier. #[derive(Clone, Debug)] pub struct DeviceIdentifier { /// The Vendor ID. pub vendor_id: u16, /// The Device ID. pub device_id: u16, } /// An error encountered while parsing a USB device identifier #[derive(thiserror::Error, Debug)] pub enum DeviceIdentifierFromStrError { #[error("could not split input by colon; expected output similar to `lsusb`")] CouldNotSplitByColon, #[error("could not parse hex from vendor or device ID")] Hex(#[from] hex::FromHexError), #[error("could not decode u64 from bytes: {0:?}")] BadBytes(Vec), } impl FromStr for DeviceIdentifier { type Err = DeviceIdentifierFromStrError; fn from_str(s: &str) -> Result { let Some((first, last)) = s.split_once(':') else { return Err(DeviceIdentifierFromStrError::CouldNotSplitByColon); }; let vendor_id = u16::from_be_bytes( hex::decode(first)? .try_into() .map_err(DeviceIdentifierFromStrError::BadBytes)?, ); let device_id = u16::from_be_bytes( hex::decode(last)? .try_into() .map_err(DeviceIdentifierFromStrError::BadBytes)?, ); Ok(Self { vendor_id, device_id, }) } }