#![allow(clippy::redundant_else)] use clap::Parser; use eyre::WrapErr; use std::io::Write; mod cli; mod vm; use vm::{SpawnArguments, VirtualMachine}; fn main() -> eyre::Result<()> { color_eyre::install()?; let mut args = std::env::args().collect::>(); let ignore_opts = String::from("--"); if let Some(run_pos) = args.iter().position(|e| e == "run") { if !args.contains(&ignore_opts) && args.get(run_pos + 1).is_some_and(|arg| arg != "--help") { args.insert(run_pos + 1, ignore_opts); } } let opts = cli::App::parse_from(args); match opts.subcommand { cli::Commands::Start { memory } => { let spawn_args = SpawnArguments { memory: memory.clone(), ..Default::default() }; let vm = VirtualMachine::start(spawn_args)?; let pid = vm.pid(); std::fs::write(&opts.lockfile, pid.to_string()).with_context(|| { format!( "could not write PID {pid} to {lockfile}", lockfile = opts.lockfile.display(), ) })?; } cli::Commands::Stop => { let spawn_arguments = SpawnArguments::default(); let vm = VirtualMachine::load(spawn_arguments, None)?; vm.kill()?; } cli::Commands::Shell => { todo!("custom args to starting a VM and piping stdin/stdout are not yet implemented"); } cli::Commands::Status => { let spawn_arguments = SpawnArguments::default(); let mut vm = VirtualMachine::load(spawn_arguments, None)?; let result = vm.execute("guest-get-host-name", serde_json::json!({}))?; let hostname = result .get("host-name") .ok_or(eyre::eyre!("no hostname"))? .as_str() .ok_or(eyre::eyre!("hostname is not str"))?; let uptime = vm.run_command("uptime", [])?; eprintln!("hostname: {hostname}"); eprint!("{}", String::from_utf8_lossy(&uptime.0)); } cli::Commands::Attach { device } => { let spawn_arguments = SpawnArguments::default(); let mut vm = VirtualMachine::load(spawn_arguments, None)?; vm.execute_host("qmp_capabilities", serde_json::json!({}))?; vm.execute_host( "device_add", serde_json::json!({ "driver": "usb-host", "bus": "usb.0", "vendorid": device.vendor_id, "productid": device.device_id, }), )?; } cli::Commands::Push { local_path, remote_path, } => { let spawn_arguments = SpawnArguments::default(); let mut vm = VirtualMachine::load(spawn_arguments, None)?; vm.push(local_path, remote_path)?; } cli::Commands::Pull { remote_path, local_path, } => { let spawn_arguments = SpawnArguments::default(); let mut vm = VirtualMachine::load(spawn_arguments, None)?; vm.pull(remote_path, local_path)?; } cli::Commands::Run { command, args } => { let spawn_arguments = SpawnArguments::default(); let mut vm = VirtualMachine::load(spawn_arguments, None)?; let (response, exit_code) = vm.run_command(&command, args)?; std::io::stdout().write_all(&response)?; std::process::exit( i32::try_from(exit_code).context(eyre::eyre!("bad PID: pid < i32::MAX << 1"))?, ); } } Ok(()) }