diff --git a/src/config.rs b/src/config.rs index 6905ff4..40ba6c0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -110,6 +110,8 @@ pub struct Target { /// Arch to run #[serde(default = "Target::default_arch")] pub arch: String, + /// Command used to launch QEMU + pub qemu_command: Option, /// Command to run inside virtual machine. pub command: String, @@ -139,6 +141,7 @@ impl Default for Target { kernel_args: None, rootfs: Self::default_rootfs(), arch: Self::default_arch(), + qemu_command: None, command: "".into(), vm: VMConfig::default(), } diff --git a/src/main.rs b/src/main.rs index 8f751dd..2b98e5d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,6 +39,11 @@ struct Args { /// Arch to run #[clap(short, long, default_value = ARCH, conflicts_with = "config")] arch: String, + /// Command to use to launch QEMU. Can be a full path or a PATH-resolved binary. If none is + /// provided, we default to `qemu-system-$ARCH`, where $ARCH is the value of the `arch` + /// argument + #[clap(short, long, conflicts_with = "config")] + qemu_command: Option, /// Command to run in kernel mode. `-` to get an interactive shell. #[clap(conflicts_with = "config")] command: Vec, @@ -113,6 +118,7 @@ fn config(args: &Args) -> Result { rootfs: args.rootfs.clone(), arch: args.arch.clone(), kernel_args: args.kargs.clone(), + qemu_command: args.qemu_command.clone(), command: args.command.join(" "), vm: VMConfig::default(), }], diff --git a/src/qemu.rs b/src/qemu.rs index 24323ef..d1bcfdb 100644 --- a/src/qemu.rs +++ b/src/qemu.rs @@ -6,7 +6,7 @@ use std::ffi::{OsStr, OsString}; use std::fs; use std::hash::Hash; use std::hash::Hasher; -use std::io::{BufRead, BufReader, Read, Write}; +use std::io::{BufRead, BufReader, ErrorKind, Read, Write}; use std::marker::Send; use std::os::unix::fs::PermissionsExt; use std::os::unix::net::UnixStream; @@ -655,8 +655,13 @@ impl Qemu { let qmp_sock = gen_sock("qmp"); let command_sock = gen_sock("cmdout"); let (init, guest_init) = gen_init(&target.rootfs).context("Failed to generate init")?; + let program = target + .qemu_command + .unwrap_or_else(|| format!("qemu-system-{}", target.arch)); + Self::verify_qemu_exists(&program)?; - let mut c = Command::new(format!("qemu-system-{}", target.arch)); + // Start the main QEMU process + let mut c = Command::new(program); c.args(QEMU_DEFAULT_ARGS) .stderr(Stdio::piped()) @@ -974,6 +979,26 @@ impl Qemu { err } + fn verify_qemu_exists(qemu_program: &str) -> anyhow::Result<()> { + if let Err(e) = Command::new(&qemu_program) + .arg("--help") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + { + if let ErrorKind::NotFound = e.kind() { + Err(e).context(format!( + "Did not find QEMU binary {qemu_program}. Make sure QEMU is installed" + )) + } else { + warn!("Failed to verify that qemu is installed due to error, continuing... {e}"); + Ok(()) + } + } else { + Ok(()) + } + } + /// Boot the VM and connect to QGA /// /// Returns the child process and QGA wrapper on success, an error otherwise.