From bdad8d32c1de3e33d28e36ff825ee8c71d5d0079 Mon Sep 17 00:00:00 2001 From: Manu Bretelle Date: Wed, 24 Jan 2024 17:41:21 -0800 Subject: [PATCH] qemu: Run a VM in interactive mode When the magic command `-` is passed, and we are running in kernel mode, get a shell prompt in the VM. To do this, we can re-use the existing `run` framework with some changes: - standard streams to the qemu command are not changed and instead are inherited - child std is not streamed to the UI anymore. - the command is not sent over qga anymore, we just wait for the qemu command to return (e.g when user exit its bash prompt) and exit Fixes #54 Tested by running ``` RUST_LOG=debug cargo run -- -k kernels/bzImage-x86_64 - ``` and confirming no existing tests are broken. Also could get it to work cross-platform: ``` cargo run -- -k ./kernels/Image-arm64 -r ./rootfs/ubuntu-lunar-arm64 -a aarch64 - ``` Signed-off-by: Manu Bretelle --- src/qemu.rs | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/qemu.rs b/src/qemu.rs index b8a7b81..fb4d7ea 100644 --- a/src/qemu.rs +++ b/src/qemu.rs @@ -35,6 +35,7 @@ const COMMAND_TEMPLATE: &str = include_str!("init/command.template"); const ROOTFS_9P_FS_MOUNT_TAG: &str = "/dev/root"; const SHARED_9P_FS_MOUNT_TAG: &str = "vmtest-shared"; const COMMAND_OUTPUT_PORT_NAME: &str = "org.qemu.virtio_serial.0"; +const MAGIC_INTERACTIVE_COMMAND: &str = "-"; const SHARED_9P_FS_MOUNT_PATH: &str = "/mnt/vmtest"; const MOUNT_OPTS_9P_FS: &str = "trans=virtio,cache=loose,msize=1048576"; @@ -625,9 +626,6 @@ impl Qemu { let mut c = Command::new(format!("qemu-system-{}", target.arch)); c.args(QEMU_DEFAULT_ARGS) - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) .arg("-serial") .arg("stdio") .args(kvm_args(&target.arch)) @@ -678,7 +676,7 @@ impl Qemu { ); } - Ok(Self { + let mut qemu = Self { process: c, qga_sock, qmp_sock, @@ -691,7 +689,27 @@ impl Qemu { _init: init, updates, image: target.image.is_some(), - }) + }; + + // We still need to possibly redirect the standard streams. + // By default, when calling `spawn`, the child process inherits + // the standard streams of the parent. This is not what we want + // when running in non-interactive mode. + if !qemu.interactive() { + qemu.process + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + } + Ok(qemu) + } + + /// Return whether or not this target is ran in interactive mode + /// + /// Interactive mode means we are not running a command and we are not + /// running in image mode. + fn interactive(&self) -> bool { + self.command == MAGIC_INTERACTIVE_COMMAND && !self.image } /// Waits for QMP and QGA sockets to appear @@ -940,7 +958,11 @@ impl Qemu { return Err(e).context("Failed to spawn QEMU"); } }; - Self::stream_child_output(self.updates.clone(), &mut child); + if !self.interactive() { + // If we are running a command, we need to stream stdout + // to the receiver. + Self::stream_child_output(self.updates.clone(), &mut child); + } // Ensure child is cleaned up even if we bail early let mut child = scopeguard::guard(child, Self::child_cleanup); @@ -1040,6 +1062,14 @@ impl Qemu { return; } + // At this stage qemu should be prompting us with a shell prompt if running + // in interactive mode. + // Once the child has returned, we are done and can exit. + if self.interactive() { + child.wait().expect("command wasn't running"); + return; + } + // Run command in VM let _ = self.updates.send(Output::CommandStart); match self.run_command(&qga) {