Skip to content

Commit

Permalink
qemu: Run a VM in interactive mode
Browse files Browse the repository at this point in the history
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 <chantr4@gmail.com>
  • Loading branch information
chantra committed Jan 25, 2024
1 parent 7b055f6 commit bdad8d3
Showing 1 changed file with 36 additions and 6 deletions.
42 changes: 36 additions & 6 deletions src/qemu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -678,7 +676,7 @@ impl Qemu {
);
}

Ok(Self {
let mut qemu = Self {
process: c,
qga_sock,
qmp_sock,
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit bdad8d3

Please sign in to comment.